|
Object-oriented
|
Object-oriented programming is a language-independent technique. While there are many languages that make OOP easier than ANSI C, that doesn't mean a project written entirely in C cannot enjoy at least some OO benefits. This article discusses how programmers can start learning OO programming techniques using plain ANSI C. (1,800 words)
Mail this article to a friend |
After years of relative obscurity, object orientation (OO) is entering the mainstream for software developers. What used to be locked up in academia and research labs is now being unleashed on unsuspecting masses. If you're observant, you can notice management cringing at the thought of complete retraining of a development team already proficient in structured programming.
Training in object-oriented programming (OOP) is important, but what good is it if you don't have any expertise in a language that supports OO? That means you'll need training in an object-oriented language. After all, don't you have to use C++, Objective C, Smalltalk or (for the adventurous) Eiffel? Nope. Remember, OOP is a language-independent technique. While there are many languages that make OOP easier than ANSI C, that doesn't mean a project written entirely in C cannot enjoy at least some OO benefits.
The basics
If you want a full explanation of what OO is, stop reading now and run
down to the local bookstore and purchase any number of quality books on
the subject -- Booch's "Object-Oriented Analysis and Design" comes to
mind. After reading the following introduction, you'll find three of
the key concepts that illustrate the advantages of the OOP approach are
encapsulation, abstraction and polymorphism.
Encapsulation is the formal term that describes the bundling of methods -- functions within an object that determine its behavior -- and data together within an object so that access to the data is permitted only through the object's own methods. No other parts of an object-oriented program can operate directly on an object's data. Communication among a set of similar or different objects occurs exclusively through messages. A message is simply a request asking the object to behave in a certain way.
OO encourages developers to think about applications in abstract terms. Applications are built thinking of sets of objects and pulling common behavior out into common objects or classes. Class libraries provide a repository for common reusable elements. Inheritance -- a method for automatically sharing methods and data among objects -- automatically maintains relationships among classes in a hierarchical format. This format is the basis for reusable class libraries.
Objects act in response to the various messages they receive. The same message can result in completely different actions when received by different objects. This is polymorphism. With polymorphism the implementation details of a given message is left up to the receiving object. For instance, assume there are two objects, a square and a circle. Sending each object the message "draw" will elicit different behavior from each object.
|
|
|
|
Some folks may get squeamish with pointers-to-functions,
but you will find they're worth the effort.
C building blocks
The question now arises, how can encapsulation, abstraction and
polymorphism be accomplished in C? The answer is surprisingly simple
and involves no concepts that can't be grabbed straight out of the C
Bible (K&R's "C Programming Language"). There's nothing up my sleeve
but structures, macros, and pointers-to-functions. Perhaps some
folks get squeamish with pointers-to-functions, but you will find
they're well worth the effort.
Objects in C
A structure is the only higher-level data object in C, so it must serve
as the basic building block of an object. Two basic graphic objects
could be defined:
typedef struct Circle { float radius; /* radius of circle */ Point location; /* Center of circle */ Color color; /* color of circle */ } Circle; typedef struct Square { float length; /* size of one side */ Point location; /* lower left hand corner */ Color color; /* foreground/outline color */ } Square;
This type of usage is nothing new nor is it particularly OO, but these objects can be extended to contain methods to help encapsulate the data. A minimum set of methods to add to both objects are:
The Square object would need a SetLength function while the Circle needs SetRadius.
By adding these methods to each class we have successfully encapsulated the data. Admittedly, there is nothing in C that prevents a developer from directly manipulating the attributes of each object, but we have provided methods that encourage proper usage.
To complete the encapsulation each class should add some message
functions such as Draw()
and Destroy()
.
After years of relative obscurity,
object orientation (OO) is entering the
mainstream for software developers.
Abstraction and inheritance
This is all well and good but what if we wanted to extend the graphics
classes to include something like shading? We have to edit both
classes and add the proper attribute and associated method. This is
error prone and is one of the many problems OOP is supposed to solve.
What we need to do is abstract common characteristics from the two
objects into a common base class. In this simple example, the only
attributes not common are length and radius so every other
attribute may be grouped into the base class. With the help of a
simple macro, a Shape base class is created:
#define SHAPE ulong shade; /* 3-D type shading */ \ Point location; /* lower left hand corner */ \ Color color; /* foreground/outline color */ \ Status *(SetShade)(Shape *, ulong); /* set 3d */ \ Status *(SetColor)(Shape *, Color); /* main color of object */ \ Status *(SetLoc)(Shape *, Point); /* base location */ \ Status *(Draw)(Shape *); /* size of the object */ \ Status *Destroy(Shape * ) /* remove an object */ typedef struct Shape { SHAPE; } Shape; typedef struct Circle { SHAPE; float radius; Status SetRadius(Circle *); } Circle; typedef struct Square { SHAPE; float length; Status SetLength(Square *); } Square;
Any additions of attributes and/or methods common to Circle and Square are now added to the macro SHAPE.
Nuts and bolts
A little divergence from straight OO is needed here to illustrate some
of the mechanics involved in making a system like this work.
Each object requires an initialization function. This function is responsible for setting up both common and unique function pointers. Assuming there are files named circle.c, square.c and common.c:
common.c /* Define functions common to all classes */ Status SetShade(Shape *shape, ulong shade) { shape->shade = shade; return(SUCCESS); } Status SetColor(Shape *shape, Color c) { shape->color = c; return(SUCCESS); } Status SetLoc(Shape *shape, Point p) { shape->location = p; return(SUCCESS); } Status Destroy(Shape *shape) { free(shape); } circle.c /* define functions only for Circles */ static Status Draw(Shape *shape) { Circle *circle = (Circle *)shape; /* some sort of drawing code here */ } static SetRadius (Shape *shape, float r) { Circle *circle = (Circle *)shape; circle->radius = r; return(SUCCESS); } Circle * InitCircle(void) { Circle *circle = (Circle *)malloc(sizeof(Circle)); circle->SetShade = SetShade; circle->SetColor = SetColor; circle->SetLoc = SetLoc; circle->Draw = Draw; circle->Destroy = Destroy; circle->SetRadius = SetRadius; /* any other initial values set here */ } square.c /* define functions only for Squares */ static Status Draw(Shape *shape) { Square *sq = (Square *)shape; /* some sort of drawing code here */ } static SetLength (Shape *shape, float r) { Square *sq= (Square *)shape; square->Length= r; return(SUCCESS); } Square * InitSquare(void) { Square *sq= (Square *)malloc(sizeof(Square)); sq->SetColor = SetColor; sq->SetLoc = SetLoc; sq->Draw = Draw; sq->Destroy = Destroy; sq->SetLenth = SetLength; /* any other initial values set here */ }
In circle.c and square.c, static functions are used to save name space.
Polymorphism
Polymorphism should be obvious at this point. Now a developer can tell
a shape to draw and one of two things occur: Either a
circle is drawn or a square. A fragment of a simple main function:
int main(argc, argv) { int i, maxshapes; Shape *shape[MAXSHAPES]; /* lots of setup code */ /* read in a list of Shape's */ maxshapes = ReadShapes(shape); /* Draw the shapes */ for(i = 0; i < maxshapes; i++) shape[i].draw(shape[i]); /* Cleanup */ for(i = 0; i < maxshapes; i++) shape[i].destroy(shape[i]); }If new Shape's are added later, main need not be changed as it only deals with the highest level of abstraction. This also implies that as new attributes are added existing code should continue to work. This makes enhancements and bug fixes much simpler as only those modules directly changed need be retested.
A smart transition
All it takes is little bit of developer discipline, macros, structures,
and pointers to functions to implement many of the basic concepts of
OOP in ANSI C. Is this a suitable method for developing large,
complicated systems? Definitely not. It relies too much on common
development guidelines. But in many C-only groups, it may come in
handy when deployed in moderation to show some quick benefits. Once
benefit is shown, it should make it much easier to sell management
on the transition to a true OO language.
|
Resources
About the author
Dave St.Clair (dave.st.clair@sunworld.com) has been writing Unix software for a living for more than a dozen years and was bit by the OOP bug while learning C++ sometime in the 80's. He now splits his time between preaching the virtues of OOP and converting the masses to use Configuration Management tools effectively. St.Clair wrote a comparative review of CM tools in the July issue of SunWorld Online.
Reach Dave at dave.st.clair@sunworld.com.
If you have technical problems with this magazine, contact webmaster@sunworld.com
URL: http://www.sunworld.com/swol-10-1995/swol-10-ooc.html
Last modified: