next up previous
Next: 3rd version (Coupling) Up: Vehicles and Garages Previous: Original version

2nd version (Consistency)

Consistency is fundamental in class design and implementation. We can speak about external consistency (in the interface) and internal consistency (in the implementation of each objects state.

When inheritance is used, there is a further issue of consistency to consider: consistency in the interface between a base class and its derived classes. In addition to the public interface, a base class may have a protected interface consisting of the protected members accessible from derived classes. By declaring protected members, a base class provides special access for use by its derived classes. The derived classes must use this access consistently with the implementation of the base class.

Car and Truck make inconsistent use of the base protected member plate. The default constructor of Vehicle set plate to 0 pointer. However identify() function in Car and Truck pass plate to printf vithout checking for a null value. If the parameter for null value, the behaviour of printf is not defined.

The following code has an undefined result:

Truck t;
t.identify();

We should fix this inconsistency.

#include <stdio.h>
#include <string.h>

class Vehicle
{
public:
    Vehicle() { plate = 0; }
    Vehicle(char *p)
    {
        plate = new char[strlen(p)+1];
        strcpy(plate, p);
    }
   ~Vehicle() { delete [] plate; }
    virtual void identify()	{ printf("generic vehicle\n"); }

protected:
    char   *plate;
};

The root of the problem is in the identify() member function in the derived classes. They behave inconsistently with respect to the protected interface of their base class. The base class represents a missing license plate number with a null pointer, but the derived identify() functions assume that the pointer is always non-null.

A derived class cannot be coded before its base class. A derived class should conform consistently to the conventions established by its base class. In our example, the derived classes do not account for all of the legitimate states of the base part of their objects.

To correct the bug, Car::identify() and Truck::identify() should test for null pointer and take a reasonable action to handle that case.

class Car : public Vehicle
{
public:
    Car() : Vehicle() { }
    Car(char *p) : Vehicle(p) { };
    void identify() 
	{ 
		char *p = plate ? plate : "<node>";
		printf("car with plate %s\n", p); 
	}
};

class Truck : public Vehicle
{
public:
    Truck() : Vehicle() { }
    Truck(char *p) : Vehicle(p) { };
    void identify() 
	{ 
		char *p = plate ? plate : "<node>";
		printf("truck with plate %s\n", p); 
	}
};

An other important point to see is, that Vehicle has a non-virtual destructor. We know, that for a polymorphic type a virtual destructor is very important. However, if the derived classes have no destructor defined, the non-virtual base destructor causes no problem.As long as the derived classes do not need destructors, the base class may remain as it is.

(This rule does not adequately address multiply inheritance.)

class Garage
{
public:
    Garage(int max);
   ~Garage();

    int       accept(Vehicle*);
    Vehicle  *release(int bay);
    void      listVehicles();
private:
    int	      maxVehicles;
    Vehicle **parked;
};

Garage::Garage(int max)
{
    maxVehicles = max;
    parked = new Vehicle*[maxVehicles];
    for( int bay = 0; bay < maxVehicles; ++bay ) 
    {
        parked[bay] = 0;
    }
}

Garage::~Garage()
{
    delete [] parked;
}

int Garage::accept(Vehicle *veh)
{
    for( int bay = 0; bay < maxVehicles; ++bay )
	if( !parked[bay] )
	{
        parked[bay] = veh;
	    return( bay );
	}
    return( -1 );	// No free bay
}

Vehicle	*Garage::release(int bay)
{
    if( bay < 0 || bay > maxVehicles )
        return 0;
    Vehicle *veh = parked[bay];
    parked[bay] = 0;
    return( veh );
}

void	Garage::listVehicles()
{
    for( int bay = 0; bay < maxVehicles; ++bay )
	if( parked[bay] )
	{
        printf("Vehicle in bay %d is: ", bay);
	    parked[bay]->identify();
	}
}

Car c1("AAA100");
Car c2("BBB200");
Car c3("CCC300");

Truck t1("TTT999");
Truck t2("SSS888");
Truck t3("UUU777");


int main( )
{
    Garage Park(14);

    Park.accept(&c1);
    int t2bay = Park.accept(&t2);
    Park.accept(&c3);
    Park.accept(&t2);

    Park.release(t2bay);

    Park.listVehicles();

    return 0;
}

The output of the program is the following:

Vehicle in bay 0 is: car with plate AAA100
Vehicle in bay 2 is: car with plate CCC300
Vehicle in bay 3 is: truck with plate SSS888


next up previous
Next: 3rd version (Coupling) Up: Vehicles and Garages Previous: Original version
Porkoláb Zoltán 2001-09-03