balance -= amt;

    }

    else

        cout << "Credit limit exceeded. Transaction cancelled.\n";

}

Using protected data members may simplify writing the code, but it has a design defect. For example, continuing with the BrassPlus example, if balance were protected, you could write code like this:

void BrassPlus::Reset(double amt)

{

    balance = amt;

}

The Brass class was designed so that the Deposit() and Withdraw() interface provides the only means for altering balance. But the Reset() method essentially makes balance a public variable as far as BrassPlus objects are concerned, ignoring, for example, the safeguards found in Withdraw().

Caution

You should prefer private to protected access control for class data members, and you should use base-class methods to provide derived classes access to base-class data.

However, protected access control can be quite useful for member functions, giving derived classes access to internal functions that are not available publicly.

Abstract Base Classes

So far you’ve seen simple inheritance and the more intricate polymorphic inheritance. The next step in increasing sophistication is the abstract base class (ABC). Let’s look at some programming situations that provide the background for ABCs.

Sometimes applying the is-a rule is not as simple as it might appear. Suppose, for example, you are developing a graphics program that is supposed to represent, among other things, circles and ellipses. A circle is a special case of an ellipse: It’s an ellipse whose long axis is the same as its short axis. Therefore, all circles are ellipses, and it is tempting to derive a Circle class from an Ellipse class. But when you get to the details, you may find problems.

To see this, first consider what you might include as part of an Ellipse class. Data members could include the coordinates of the center of the ellipse, the semimajor axis (half the long diameter), the semiminor axis (half the short diameter), and an orientation angle that gives the angle from the horizontal coordinate axis to the semimajor axis. Also the class could include methods to move the ellipse, to return the area of the ellipse, to rotate the ellipse, and to scale the semimajor and semiminor axes:

class Ellipse

{

private:

    double x;     // x-coordinate of the ellipse's center

    double y;     // y-coordinate of the ellipse's center

    double a;     // semimajor axis

    double b;     // semiminor axis

    double angle; // orientation angle in degrees

    ...

public:

    ...

    void Move(int nx, ny) { x = nx; y = ny; }

    virtual double Area() const { return 3.14159 * a * b; }

    virtual void Rotate(double nang) { angle += nang; }

    virtual void Scale(double sa, double sb)  { a *= sa; b *= sb; }

    ...

};

Now suppose you derive a Circle class from the Ellipse class:

class Circle : public Ellipse

{

    ...

};

Although a circle is an ellipse, this derivation is awkward. For example, a circle needs only a single value, its radius, to describe its size and shape instead of having a semimajor axis (a) and semiminor axis (b). The Circle constructors can take care of that by assigning the same value to both the a and b members, but then you have redundant representation of the same information. The angle parameter and the Rotate() method don’t really make sense for a circle, and the Scale() method, as it stands, can change a circle to a non-circle by scaling the two axes differently. You can try fixing things with tricks, such as putting a redefined Rotate() method in the private section of the Circle class so that Rotate() can’t be used publicly with a circle, but, on the whole, it seems simpler to define a Circle class without using inheritance:

class Circle      // no inheritance

{

private:

    double x;     // x-coordinate of the circle's center

    double y;     // y-coordinate of the circle's center

    double r;     // radius

    ...

public:

    ...

    void Move(int nx, ny) { x = nx; y = ny; }

    double Area() const { return 3.14159 * r * r; }

    void Scale(double sr)  { r *= sr; }

    ...

};

Now the class has only the members it needs. Yet this solution also seems weak. The Circle and Ellipse classes have a lot in common, but defining them separately ignores that fact.

Перейти на страницу:

Все книги серии Developer's Library

Похожие книги