Wednesday, May 21, 2008

Liskov Substitution Principle (LSP) and Design By Contract (DBC)

Adhering to OCP requires employing abstraction and inheritance. The key concern LSP promises to address is the quality of the inheritance (What? Quality of inheritance? Yes). If inheritance is not applied correctly i.e if any inheritance violates LSP, ends up violating OCP too.

In OO programming IS-A should be thought in terms of object's behavior. Following example should help us understanding it better.

Is rectangle A square? Mathematically yes when width is same as its height. So lets device and interface to work with squares and rectangles.

class RectangualShape {
public:
virtual void SetWidth( int w ) = 0;
virtual void SerHeight( int h ) = 0;
virtual int Area() = 0;
};

class Rectangle: public RectangularShape {
public:
virtual void SetWidth( int w ) { width = w; }
virtual void SetHeight( int h ) { height = h; }
virtual int Area() { return width * height; }

private:
int width;
int height;
};

class Square: public RectangularShape {
public:
virtual void SetWidth( int w ) { side = w; }
virtual void SetHeight( int h ) { side = h; }
virtual int Area() { return side* side; }

private:
int side;
};


int main()
{
int w= 1;
int h = 2;
RectangualShape *rect = new Rectangle();

rect->SetWidth( w );
rect->SetHeight( h );
assert( (w * h) == rect->Area() );

RectangualShape *sqr = new Square();

sqr->SetWidth( w );
sqr->SetHeight( h );
assert( (w * h) == sqr->Area() ); // BINGO, assertion fails

return 0;
}

Why did the assertion fail for square shape? Because a square is behaviorally different from a rectangle (except in one instance). So misusing IS-A relationship results in inheritance that violates both LSP and OCP.

The above example emphasizes the fact that derived classes should behave as advertised in the base class.

The Principle

Inheritance should ensure that any property proved about super type objects also holds for sub type objects -B. Liskov (87)

Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it - R. Martin (96)

A few key things to note about using interface are

  • Its illegal for a derived class to override a base class method with NOP (no operation)
  • Establish and document the interface contracts (DBC)
  • Any inheritance that violates LSP also violates OCP

When inheritance is involved, users of an interface (abstract base class) need not care for how a derived class behaves. Its expected that derived classes also keeps the promise made by the abstract base class. Then how does the users know what an interface is promising? Well by looking at the class, or otherwise through documentation.

Design By Contract (DBC): Its nothing but advertized behavior of an object

  • Advertized Requirements (Preconditons), i.e callers should not assume anything and should always pass valid arguments
  • Advertized Promises (Postconditons), i.e expected behavior or results

To keep the interface promise: When redefining a method in a derived class, you may only replace its precondition by a weaker one and its postcondition by a stronger one - B. Mayer (88)

Key points of DBC

  • Derived class services should require no more and promise no less
  • Document pre and post conditions
  • Because of preconditions, DBC says methods need not do any validations on input arguments
  • Invariants can be used effectively to see whether any derived class violates the base class behavior or not


class RectangualShape {
public:
/**
* Precond: Positive integer < 100
* Postcond: void
*/

// w should have been unsigned, precond should help us
virtual void SetWidth( int w ) = 0;

/**
* Precond: Positive integer < 100
* Postcond: void
*/
virtual void SerHeight( int h ) = 0;

/**
* Precond: void
* Postcond: w * h
*/
virtual int Area() = 0;
};

References:

Liskov Substitution Principle - PDF
Design Principles and Design Patterns - PDF
Principles of Object Oriented Design - PPT
Advanced Principles of OO Class Design - PPT

Thursday, May 15, 2008

C++ tidbit - virtual functions and default arguments

Virtual functions are bound dynamically where as default arguments are bound statically

class Base {
public:
virtual void func( int x = 10 ) {
_x = x;
std::cout << _x;
};

private:
int _x;
};

class Derived: public Base {
public:
virtual void func( int x = 20 ) {
_x = x;
std::cout << _x;
};

private:
int _x;
};

int main()
{
Base *b = new Base();
std::cout << "Default Value in Base: ";
b->func();
std::cout << std::endl;

Base *d = new Derived();
std::cout << "Default Value in Derived: ";
d->func();
std::cout << std::endl;

return 0;
}

Output:

Default Value in Base: 10
Default Value in Derived: 10

Default arguments of virtual methods in the base gets statically bound (in our case Base::func() ), hence never override the default values in derived virtual methods, doing so might confuse us with derived object's default behavior.