CSC/ECE 517 Fall 2009/wiki3 20 i7: Difference between revisions

From Expertiza_Wiki
Jump to navigation Jump to search
Line 73: Line 73:
<pre>
<pre>


MyList<T> implements List<T> {
SelectiveCountingList<T> implements List<T> {
    ...
    public boolean contains(T element) {
        this.remove(t)
        // -- Wait!  I can't do that, a postcondition to calling contains is that the list is unmodified.
        //          The removal of this postcondition in this list would mean this class has less postconditions then the parent.
        //          This is not good!
        ...
    }
    ...
}
}



Revision as of 01:39, 19 November 2009

Liskov substitution principle

Definition

The Liskov substitution principle is a object oriented design principle was introduced by Barbara Liskov in 1987 and is concerned with subtyping and contractual adherence. This principle is part of the SOLID principles of object oriented design which is an acronym for the five basic class design principles and relates to many object oriented design principles. The basic idea behind the object oriented design principles is to create and apply proper abstractions. Based on a paper from 1994 the principle states,

"Let q(x) be a property provable about objects x of type T. Then q(y) should be true for objects y of type S where S is a subtype of T.".

This means that for every parent type T, subtype S should be able to be substituted in for it and the behavior of the program should remain exactly the same. Plainly, we want subtypes to be substitutable for their base types as Robert Martin put it. The following example in Java illustrates the principle.


public class MyClass {
    private final List<String> list;

    public MyClass(List<String> list) {
        this.list = list;
    }
    ....
}

public class TestRunner {
    public static void main ( String[] args ) {
        // If the List type follows Liskov substitution...
        List<String> arrayList = new ArrayList<String>();
        List<String> linkedList = new LinkedList<String>();

        // ...I should be able to use any subtype of List to create a new MyClass and observe identical behavior!
        MyClass this = new MyClass(arrayList);
        MyClass works = new MyClass(linkedList);
    }
}

Preconditions

When dealing with Liskov substitution, preconditions cannot be strengthened. Intuitively, the subtype cannot require anymore preconditions than the parent. This makes sense because if the subtype required more than the parent, then the subtype would not be able to be effectively substituted in for the parent transparently. There would need to be more preconditions satisfied after substituting in the subtype in this hypothetical situation. Take for instance this example in Java which illustrates this point.


interface LocationProvider {
    /**
     *
     * Preconditions: None
     *
     */
    public void getLocation();
}

class GpsLocationProvider implements LocationProvider {

    /**
     *
     * Preconditions: A GPS fix must be present.  -- Wait! I can't do that because I would be requiring more 
     *                                               than what the interface requires which 
     *                                               violates this principle.  Doing this would mean others 
     *                                               would need to know that the underlying implementation 
     *                                               of a LocationProvider is a GpsLocation provider and 
     *                                               to get a GPS fix before calling getLocation(), 
     *                                               we don't want that!
     *
     */
    public void getLocation() {
        ...
    }
}

Postconditions

When dealing with Liskov substitution, postconditions cannot be weakened. Intuitively, the subtype cannot have fewer postconditions than the parent. This makes sense because if the subtype did not at least adhere to the postconditions of the parent, it could not be substituted for it simply because postconditions we assumed to be true would not be. Take for instance this example in Java which illustrates this point.


SelectiveCountingList<T> implements List<T> {
    ...
    public boolean contains(T element) {
        this.remove(t)
        // -- Wait!  I can't do that, a postcondition to calling contains is that the list is unmodified.
        //           The removal of this postcondition in this list would mean this class has less postconditions then the parent.
        //           This is not good!
        ...
    }
    ...
}

Invariants

When dealing with Liskov substitution, invariants must be preserved. In other words, properties that are assumed to be true must always remain true. This property lends itself to being self explained by the quality of correctness. Take for instance this example in Java which illustrates this point.


Rectangle/Square example

Conclusion

The Liskov substitution principle is a guideline on subtyping existing classes. The purpose of Liskov substitution is to ensure that subtypes adhere to their contracts.


Concisely:

  • Subtypes must not require anymore preconditions as it violates the contract they are based on.
  • Subtypes must have at least the same set of postconditions to adhere to the contract they are based on.
  • Subtypes must have the same invariants to ensure consistency.


Using these properties subtypes can be substituted for base types.

References