News:

This week IPhone 15 Pro winner is karn
You can be too a winner! Become the top poster of the week and win valuable prizes.  More details are You are not allowed to view links. Register or Login 

Main Menu

c++ tips - Beating a dead rectangle

Started by ben2ong2, October 07, 2006, 04:00:31 AM

Previous topic - Next topic

0 Members and 2 Guests are viewing this topic.

ben2ong2

[ This is an ongoing discussion of inheritance reflected in a debate
  over rectangles and squares. See also <subtyp> and <square> ]

RESPONSE: You are not allowed to view links. Register or Login (Bob Martin), 17 Mar 93

[...]

Often we deal with mathematical concepts, and find that they don't
jibe quite right.  The Square/Rectangle issue is just one of many such
connundrums. 

Several people suggested that, by the above definition, A rectangle
ISA square since anything that can be done to a square can also be
done to a rectangle.

But John Skaller said it best when he noted that it depends upon what
you are trying to do.

For example:

class Rectangle
{
  public:
    Rectangle(double height, double width)
    : itsHeight(height)
    , itsWidth(width)
    {}

    double Area() const {return itsHeight * itsWidth;}

  private:
    double itsHeight, itsWidth;
};


class Square : public Rectangle
{
  public:
    Square(double side)
    : Rectangle(side, side)
    {}
};

There is nothing wrong with this; other than that Square is not as
dense as it could be.  In this case, both the square and
the rectangle are merely descriptive.  Thus, the Square ISA Rectangle.

However, if we add:

void Rectangle::Stretch(double width, double height)
{
    itsHeight *= height;
    itsWidth *= width;
}

Now we have a problem.  Square simply cannot afford to inherit this
function since its invocation could place the square in an state which
is not compatible with being a square.  (Its sides could be made unequal)

Thus, Square can no longer sustain the ISA relationship with
Rectangle...

Should we turn it around??? Should Rectangle derive from Square.
Consider the following:

class Square
{
  public:
    Square(double side)
    : itsSide(side)
    {}

    virtual double Area() const {return itsSide*itsSide;}
   
  protected:
    double itsSide;
};

class Rectangle
{
  public:
    Rectangle(double width, double height)
    : Square(width) // ICK!
    : itsHeight(height)
    {}

    virtual double Area() const {return itsSide * itsHeight;} // ICK!

    void Stretch(double height, double width)
    {
        itsSide *= width;   // ICKY ICKY!!
        itsHeight *= height;
    }

  private:
   double itsHeight;
};


This is revolting.  We are using a member variable of Square for a
purpose it was never intended to serve.  itsSide was supposed to
represent the length of the side of a Square, but objects of class
rectangle are using it to represent the width of the rectangle. 

This is a kludge, and will be a source of future errors. 

In support of the kludge theory was the necessity of making the member
variable "protected".  This, in a way, admited that definition of the
variable was ambiguous.

Finally, reading the code is not easy since one must know that a
"trick" is being played on class Square before the Rectangle code can
be properly understood.

=-=-=-=-=-=-=-=-

So what is the best model?  It depends.  I would separate these two
objects completely.  Perhaps by deriving them from the following
abstract base:

class ObjectWithArea
{
  public:
    virtual double Area() const = 0;
};

class Square : public ObjectWithArea {...};
class Rectangle : public ObjectWithArea {...};

But like John said, it depends upon what you are trying to do.
You are not allowed to view links. Register or Login
You are not allowed to view links. Register or Login