473,503 Members | 1,629 Online
Bytes | Software Development & Data Engineering Community
+ Post

Home Posts Topics Members FAQ

LSP and programming by contract

11,448 Recognized Expert MVP
Greetings,

This tip of the week introduces a bit of theory when it comes to Object Oriented
programming. First we indroduce a little example project: a simple geometry
project. We want to define a bunch of classes that represent resizable shapes.
Nothing complicated much: we define an abstract Shape class and there we go:
Expand|Select|Wrap|Line Numbers
  1. public abstract class Shape { 
  2.    abstract public double getArea();
  3.    ... 
  4. }
  5. public class Rectangle extends Shape {
  6.    // it's dimensions
  7.    private double width, height;
  8.    // contructor
  9.    public Rectangle(double width, double height) {
  10.       this.width= width;
  11.       this.height= height;
  12.    }
  13.    // getters and setters:
  14.    public double getWidth() { return width; }
  15.    public double getHeight() { return height; }
  16.    public void setWidth(double width) { this.width= width; }
  17.    public void setHeight(double height) { this.height= height; }
  18.    // get the area
  19.    public double getArea() { return width*height; }
  20. }
The abstract shape class doesn't know how to calculate an area, thus the method
is made abstract. The Rectangle defines this method. Because a Rectangle *is-a*
Shape, we extend from the abstract class shape. This little scenario can be
found in almost any beginner's textbook; it's almost boring to look at this
for the umptiest time.

We finish this boring little project by defining a Square. As we all know a Square
*is-a* special type of Rectangle, so we extend the Rectangle class:
Expand|Select|Wrap|Line Numbers
  1. public class Square extends Rectangle {
  2.    // constructor
  3.    public Square(double side) { super(side, side); }
  4.    // getters and setters:
  5.    public double getSide() { return getWidth(); } // or getHeight()
  6.    public void setSide(double side) {
  7.       super.setWidth(side);
  8.       super.setHeight(side);
  9.    }
  10.    public void setWidth(double width) { this.setSide(side); }
  11.    public void setHeight(double width) { this.setSide(side); }
  12. }
So there: finished. We used most of the Rectangle code: we didn't even had to
define a new getArea() method and everything is fine. We wrap everything up,
build a .jar file, burn it on a CD (we even stick a nice label on it), send
the CD and the bill to our customer and we're going to enjoy the weekend.

That same day our customer receives the CD, installs everything and just
because he's a nasty nitpicker, he wants to test it:
Expand|Select|Wrap|Line Numbers
  1. public static void main(String[] args) {
  2.    Rectangle r= new Rectangle(1.0, 0.0);
  3.    for (int i= 1; i <= 10; i++) {
  4.       r.setHeight(i);
  5.       if (r.getArea() != i)
  6.          System.err.println("This software is not correct!);
  7.    }
  8. }
Later that day our customer gives us a call and tells us that the tests had
passed fine so far. We know our software is fine and we congratulate our
customer with his new, fine, correct software.

Our customer also knows that a Square *is-a* special type of Rectangle so he
crafts his next test:
Expand|Select|Wrap|Line Numbers
  1. public static void main(String[] args) {
  2.    Rectangle r= new Square(1.0);
  3.    for (int i= 1; i <= 10; i++) {
  4.       r.setHeight(i);
  5.       if (r.getArea() != i)
  6.          System.err.println("This software is not correct!);
  7.    }
  8. }
Guess what? Later that day our customer calls again: he's furious; nine out
of ten test runs failed; just the unit square test succeeded. All he did was
instantiating a Square instead of a Rectangle and because a Square *is-a*
Rectangle everything is supposed to work correctly but it doesn't.

We decide to skip that bill for the moment and have another look at our
classes; there goes the weekend.

Before midnight comes we realize that we're on the wrong track:
no matter what we do to keep the width and height have equal values, the
getArea() gives unexpected results if we consider our Square to be a special
kind of Rectangle. We can't do this either:
Expand|Select|Wrap|Line Numbers
  1. public class Square extends Rectangle {
  2.    ...
  3.    public void setWidth(double width) {
  4.       throw IllegalArgumentException("Don't use this method for Squares!");
  5.    }
  6.    public void setHeight(double height) {
  7.       throw IllegalArgumentException("Don't use this method for Squares!");
  8.    }
  9. }
This hack most certainly doesn't pass our customer's test suite. But a Square
*is-a* special kind of Rectangle. Or is it?

In 1988 Barabara Liskov made the following observation about this problem:

"What is wanted here is something like the following substitution property:
If for each object o2 of type D there is an object o1 of type B such that for
all programs P defined in terms of B, the behavior of P is unchanged when o2
is substituted for o1 then D can be a subtype of B."

- Data Abstraction and Hierarchy, SIGPLAN Notices, 23,5 (May, 1988).

Barabara Liskov was the mathematical kind of type and mathematicians are like
the Greek: you give them a problem, they translate it to their own language
and all of a sudden you don't understand your own problem anymore. (*)

For us regular folks who might wear tennis shoes or an occasional python boot,
she said this: the program P is our customer's test suite (see above). The o1
object is an object from the Rectangle class. Our customer's test suite works
fine with them (remember the first happy phone call?) If something wants to
be a derived class (object o2 from class D which in our example is a Square)
that same program should continue to work fine on it.

This is all fine and dandy but our Square class (which extends a Rectangle)
doesn't pass this substitution test. And that is what Barabara Liskov's LSP
is all about: if a derived class doesn't pass this test, it shouldn't be a
derived class at all.

But then again: a Square *is-a* Rectangle. In a mathematical sense yes it is,
but in our example where we can resize those things a Square isn't a Rectangle.
The *is-a* relation isn't a mathematical relation de facto. It is a behavioural
relationship and our attempts failed miserably in the LSP test drive.

That program P expects a Rectangle to operate on, no matter whether or not
they're "special" rectangles. If it runs fine on a rectangle it should run fine
of a special Rectangle. If it doesn't run fine, that special Rectangle is not
supposed to be a Rectangle at all and is not supposed to be a subclass of the
Rectangle class. That's what the LSP is all about. It's a sort of litmus test
for your inheritance tree: if a child class fails to behave as its parent:
don't make the class a child of that parent. (it normally doesn't work that
way in the real world ;-)

So finally, here's our redesigned Square class:
Expand|Select|Wrap|Line Numbers
  1. public class Square extends Shape {
  2.    private double side;
  3.    // constructor:
  4.    public Square(double side) { this.side= side; }
  5.    // getters and setters: 
  6.    public double getSide() { return side; }
  7.    public void setSide(double side) { this.side= side; }
  8.    // the area implementation:
  9.    public double getArea() { return side*side; }
  10. }
Boring isn't it? But now it passes the LSP!

There's one thing left: why did Barbara Liskov write "then it *can be* a
subclass". The answer for Java is simple: if two classes implement the same
interface and that program P expects that interface, it should run fine
no matter which of the different implementations (classes) of that same
interface are passed to that program P.

A bit after the LSP was formulated, Bertrand Meyers came up with the
"Design By Contract" notion, which borders the LSP. Every method has its
preconditions and postconditions. When a method is invoked, its precondition
must hold; when a method completes, it promises that its postcondition holds.

As an example, the postcondition of the setWidth method in the Rectangle
class, is:

this.width == width && this.heigth == oldHeight

where oldHeight represents the value of height before the method was called.
As Meyers wrote:

"when rewriting a method (in a derived class), you may only replace the
precondition by a weaker one and replace the postcondition by a stronger one
w.r.t. the conditions of the superclass method"
.

A "weaker" precondition is true if the "stronger" precondition in the superclass
is true. In other words, when using an object through its base type, the user
(the program P in LSP) knows only the preconditions and postconditions of
the base class. Thus, derived objects must not expect such users to obey to
preconditions that are stronger than those required by the base class. That is,
they must accept anything that the base class would accept. Also, derived
classes must conform to all postconditions of the base, i.e. their behaviour
and outputs (side effects) must not violate the constraints imposed by the base class.

As can be clearly seen, the Rectangle/Square example violates the ‘stronger
postcondition’ in the Square class, i.e. the postcondition shown above for the
Rectangle class doesn’t hold in the derived Square class.

'till next time which will be "playtime": Sudoku solvers and all that.

kind regards,

Jos
May 6 '07 #1
0 6021

Sign in to post your reply or Sign up for a free account.

Similar topics

90
4743
by: Bret Pehrson | last post by:
This message isn't spam or an advertisement or trolling. I'm considering farming some of my application development to offshore shops (I'm in the US). I have absolutely *no* experience w/ this,...
6
1922
by: Ricky W. Hunt | last post by:
It's dawning on my a lot of my problems with VB.NET is I'm still approaching it in the same way I've programmed since the late 70's. I've always been very structured, flow-charted everything, used...
81
2727
by: Russ | last post by:
I just stumbled onto PEP 316: Programming by Contract for Python (http://www.python.org/dev/peps/pep-0316/). This would be a great addition to Python, but I see that it was submitted way back in...
2
1678
by: DSpark | last post by:
I was a wondering whether anyone had developed and is will to share a database to monitor business contracts and services and monitor there performance. Ideally what Im looking for is a MS Access...
4
2838
Sandboxer
by: Sandboxer | last post by:
I want to be able to program Access to provide for me, by individual day, what my contract obligations are to my customers. Will Access recognize all the individual days in between a date range...
1
8246
by: vineetbindal | last post by:
Hi All, I am new to silverlight and i am following a tutorial. In order to connect to database in silverlight application i need to add a referance to a service. which gives me an error that the...
0
7198
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However,...
0
7072
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can...
0
7319
jinu1996
by: jinu1996 | last post by:
In today's digital age, having a compelling online presence is paramount for businesses aiming to thrive in a competitive landscape. At the heart of this digital strategy lies an intricately woven...
0
7449
tracyyun
by: tracyyun | last post by:
Dear forum friends, With the development of smart home technology, a variety of wireless communication protocols have appeared on the market, such as Zigbee, Z-Wave, Wi-Fi, Bluetooth, etc. Each...
0
5570
agi2029
by: agi2029 | last post by:
Let's talk about the concept of autonomous AI software engineers and no-code agents. These AIs are designed to manage the entire lifecycle of a software development project—planning, coding, testing,...
1
4998
isladogs
by: isladogs | last post by:
The next Access Europe User Group meeting will be on Wednesday 1 May 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM). In this session, we are pleased to welcome a new...
0
3149
by: adsilva | last post by:
A Windows Forms form does not have the event Unload, like VB6. What one acts like?
0
1498
by: 6302768590 | last post by:
Hai team i want code for transfer the data from one system to another through IP address by using C# our system has to for every 5mins then we have to update the data what the data is updated ...
0
373
bsmnconsultancy
by: bsmnconsultancy | last post by:
In today's digital era, a well-designed website is crucial for businesses looking to succeed. Whether you're a small business owner or a large corporation in Toronto, having a strong online presence...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.