An abstract class is a class that has one or more abstract operations. An abstract operation is not implemented in its class but in classes that are derived from it. A class with no abstract operations is called a concrete class. An abstract class defines an incomplete framework into which you can plug in specific implementations.
One abstraction can have many different internal structures - and different performance properties. For example: A Stack can implemented by using an array or by using a linked list. It is nice to be able to express an abstraction when we are programming a complex problem. Several object-oriented languages allow you to define an abstract class and derive from it multiple implementations.
For example, the class of Animals may be abstract class and Lion may be derived from it. The Abstract Animal has an operation that returns the number of legs. Lions implement this legs() operation as returning the number 4. Similarly, a Snake has 0 legs, and a Bird 2 leg. Here is the UML diagram of the abstract class Animal with abstract operations. The italic names indicate abstract classes and operations in the UML. The diagram also shows that Lion, Snake, and Bird implement the abstract class. The dashed generalization arrow indicates this.
[ animal.dia ]
An object that is declared to be in a concrete class (a Snake, Lion, or Bird above) has defined behavior. If the concrete class implements or relizes an abstract class then the object must satisfy the abstract operations. So Animal above is realized or implemented by Lion, Snake, and Bird.
On the other hand you can not create an abstract object. Instead we define pointers that refer to the abstract type but are attached to objects in concrete classes that implement the abstract class. All animals share the legs() abstraction so we can point at any Animal and say "How many legs has that Animal got?":
Animal *thatAnimal;
Lion leo;
thatAnimal = &leo;
cout << thatAnimal->legs() << endl;
Analogies
An abstract class gives us a view of an object from a distance.
Concrete classes are up close and personal. From a distance
we can see an Animal with legs that feeds on food. Only
when we get closer can we see that the animal is a Bird
with 2 legs and a liking for bugs.
In most places in the world you can ask for a GlassOfBeer. The ammount of beer you get is different: The UK Pint is larger than the USA pint, and the metric half-liter is larger still. Still you order by the "glass". So GlassOfBeer is an abstract class that is realized differently in each country. Indeed in Geneva (Switzland) the size of a GlassOfBeer varies with the part of town you are in.
You can think of an abstract class as a socket into which you plug in code. The abstract functions are holes in the socket. The prongs in the plug are the concrete functions. The abstract class serves to organize a set of functions that are implemented in many different ways. It sets a standard that different implementations must meet. It is like a standard wall socket or jack - nothing happens unless a working device is plugged into it.
C++ Abstractions
In C++ we signal that a class is abstract
by using pure virtual functions. A pure function
in C++ is a member function of a class that is not implemented in that class.
In C++
all pure functions must be declared as virtual. For this reason C++
programmers talk about pure virtual functions rather than abstract functions.
We use normal inheritance to encode the implementation of an abstract class.
The concrete classes derived from the abstract class must overload all the pure functions and so define what the function does on objects in the derived class. Realization and implementation fill in the missing details.
The behavior of an object is defined by its concrete class. Clearly the behavior of objects in abstract classes is not defined unless they are also in a derived concrete class. So it is not possible to declare objects as elements of abstract classes. We can not do this:
Animal x; // ERRORTo declare an object in an abstract class is like buying a watch that has nothing inside it.
Neither can we ask for a new one to be created:
Animal * p = new Animal; //ERRORHowever we can have a pointer declared to point at the base class, which actually points at an object that is in a derived class
Animal * p = new Lion; //OK!Thus abstract classes are used by using pointers in place of objects. This is why in C++ you make all the pure functions virtual as well. This makes sure that the correct implementation is always chosen via a pointer to the abstract class. The compiler can sort out direct references before the program starts. The virtual functions will figure out the concrete class as the program runs.
Next, you make sure that it is impossible to create objects that are in the abstract class, except by declaring one of the implementations.
The C++ syntax is odd. (1) All functions in the abstract class must be declared as virtual. (2) At least one function is declared as being equal to zero! (3) The rest must be given implementations.
class Animal{
public:
virtual int legs()=0;//this is not a mistake!
virtual void feed(Food)=0;//output a noise?
...
};In Animal above the meaning of feeding an animal and the number of legs it has are deferred to a derived classes:
class Lion : public Animal {
public:
virtual int legs(){return 4;}
...
};
class Snake: public Animal{
public:
virtual int legs();
};
int Snake::legs(){return 0;}
...It is now possible to declare Snakes and Lions but we can not declare an Animal. However we can declare variable that points to an Animal and make it point at either a Snake or a Lion. The following uses cassert to test the classes:
// Animal a; -- illegal
Snake s; // legal
Lion l; // legal
Animal * p; // legal
p=&s; //p is the address of s, *p is s.
assert( p->legs() == 0 );
p=&l; //p is the address of l, *p is l.
assert( p->legs() == 4 );
cout << "Test OK" << endl;
return 0;You can find a simplified demonstration of these classes by downloading [ animal.cpp ] and testing it. If that is OK, try an array:
Animal * zoo[]={new Lion; new Snake, new Bird};
Money as an Example
Money is an abstraction, Dollars, Franks, Pounds, Punts, etc etc are
all concrete realizations of the idea of Money. This inspired the
following pieces of code:
[ moneyv.h ]
[ moneyv.cc ]
Concrete, Abstract, and Interface
We call a class with no pure virtual functions a concrete class. We call a
class with nothing but public pure virtual functions an
interface. Classes that have some pure functions and some
fully defined and implemented functions "abstract classes".
Interfaces
An interface describes a set of functions that a class
can use. It also define the set of functions that a class
must provide if it is to be useful. The interface becomes
usable when a concrete class is derived from it. However the
code using an interface can be written and checked out by the
compiler before the interface is implemented. Interfaces are
useful because several different interfaces can be implemented by one
class. Multiple inheritance is not a problem when there are many
interfaces and one abstract or concrete base class.
The UML Lollypop Notation
Classes that relaize many interfaces are common in complex
programs so there is a special short hand notation in the UML.
A class that implements an interface has a lollypop (---0)
growing out of it. The name of the interface is written by the lollypop.
A class that uses the interface to access a class has a dotted line that
points at the circle at the end of the lollypop.
Abstractions that Share Data
When a base class declares its public functions as virtual it allows
its derived classes to provide a definition of what these functions
actually do. Typically one base has several derived "subclasses" each
with different implementations of the same set of functions. Sometimes
it is natural for all the derived function to share some common properties.
The case study in the text, for example, shows how circles and rectangles
both have the attributes of area and perimeter, but calculate them differently.
It is not wise to make data public. In this case (derived classes need access to their base class's data) therefore the data should be declared to be protected. Protected data (and functions) can be accessed by derived classes only. This provides a measure of encapsulation - protection from arbitrary changes - while permitting a degree of coupling between classes.
. . . . . . . . . ( end of section Abstract Classes) <<Contents | End>>
Abreviations