EECE 310: Software Engineering

Slides:



Advertisements
Similar presentations
Introduction to Java 2 Programming
Advertisements

The Substitution Principle SWE 332 – Fall Liskov Substitution Principle In any client code, if subtype object is substituted for supertype object,
Composition CMSC 202. Code Reuse Effective software development relies on reusing existing code. Code reuse must be more than just copying code and changing.
Data Abstraction II SWE 619 Software Construction Last Modified, Spring 2009 Paul Ammann.
OOP in Java Nelson Padua-Perez Chau-Wen Tseng Department of Computer Science University of Maryland, College Park.
© 2006 Pearson Addison-Wesley. All rights reserved4-1 Chapter 4 Data Abstraction: The Walls.
© 2006 Pearson Addison-Wesley. All rights reserved4-1 Chapter 4 Data Abstraction: The Walls.
Data Abstraction and Object- Oriented Programming CS351 – Programming Paradigms.
OOP in Java Fawzi Emad Chau-Wen Tseng Department of Computer Science University of Maryland, College Park.
Subclasses and Subtypes CMPS Subclasses and Subtypes A class is a subclass if it has been built using inheritance. ▫ It says nothing about the meaning.
OOP Languages: Java vs C++
“is a”  Define a new class DerivedClass which extends BaseClass class BaseClass { // class contents } class DerivedClass : BaseClass { // class.
CSE 332: C++ templates This Week C++ Templates –Another form of polymorphism (interface based) –Let you plug different types into reusable code Assigned.
Inheritance. Recall the plant that we defined earlier… class Plant { public: Plant( double theHeight ) : hasLeaves( true ), height (theHeight) { } Plant(
1 Abstraction  Identify important aspects and ignore the details  Permeates software development programming languages are abstractions built on hardware.
CSSE501 Object-Oriented Development. Chapter 12: Implications of Substitution  In this chapter we will investigate some of the implications of the principle.
EECE 310: Software Engineering Iteration Abstraction.
Design Patterns Gang Qian Department of Computer Science University of Central Oklahoma.
Chapter 3 Inheritance and Polymorphism Goals: 1.Superclasses and subclasses 2.Inheritance Hierarchy 3.Polymorphism 4.Type Compatibility 5.Abstract Classes.
Object Oriented Software Development
Types in programming languages1 What are types, and why do we need them?
Type Abstraction Liskov, Chapter 7. 2 Liskov Substitution Principle In any client code, if the supertype object is substituted by a subtype object, the.
Data Abstractions EECE 310: Software Engineering.
Type Abstraction SWE Spring October 05Kaushik, Ammann Substitution Principle “In any client code, if supertype object is substituted.
Inheritance. Inheritance - Introduction Idea behind is to create new classes that are built on existing classes – you reuse the methods and fields and.
Object-Oriented Programming Chapter Chapter
Polymorphism Liskov 8. Outline equals() Revisiting Liskov’s mutable vs. not rule Polymorphism Uniform methods for different types “easy” polymorphism.
Data Abstraction SWE 619 Software Construction Last Modified, Spring 2009 Paul Ammann.
1 Inheritance Reserved word protected Reserved word super Overriding methods Class Hierarchies Reading for this lecture: L&L 9.1 – 9.4.
Understanding ADTs CSE 331 University of Washington.
Classes, Interfaces and Packages
Polymorphism SWE 619. Outline equals() Revisiting Liskov’s mutable vs. not rule Polymorphism Uniform methods for different types “easy” polymorphism Element.
Quick Review of OOP Constructs Classes:  Data types for structured data and behavior  fields and methods Objects:  Variables whose data type is a class.
Written by: Dr. JJ Shepherd
CSSE501 Object-Oriented Development. Chapter 10: Subclasses and Subtypes  In this chapter we will explore the relationships between the two concepts.
Cs2220: Engineering Software Class 12: Substitution Principle Fall 2010 University of Virginia David Evans.
Terms and Rules II Professor Evan Korth New York University (All rights reserved)
Type Hierarchies. Type Hieararchy Why?: Want to define family of related types. At the top of the hierarchy, a type whose spec defines behavior common.
Recitation 7 Godfrey Tan March 21, Administrivia PS6 due Tuesday right after break Final Project descriptions will be handed out Monday after break.
Inheritance Modern object-oriented (OO) programming languages provide 3 capabilities: encapsulation inheritance polymorphism which can improve the design,
EECE 310: Software Engineering
Modern Programming Tools And Techniques-I
Data Abstraction: The Walls
EECE 310: Software Engineering
7. Inheritance and Polymorphism
Inheritance and Polymorphism
Java Primer 1: Types, Classes and Operators
Software Engineering Fall 2005
CSE 331 Subtyping slides created by Marty Stepp based on materials by M. Ernst, S. Reges, D. Notkin, R. Mercer, Wikipedia
EECE 310: Software Engineering
Interfaces and Inheritance
Type Abstraction SWE Spring 2009.
Lecture 19 - Inheritance (Contd).
Extending Classes.
Java Programming Language
Lecture 22 Inheritance Richard Gesick.
slides created by Ethan Apter
Type Abstraction Liskov, Chapter 7.
Constructors and Other Tools
slides created by Ethan Apter
Java – Inheritance.
Stacks.
Fall 2018 CISC124 2/24/2019 CISC124 Quiz 1 marking is complete. Quiz average was about 40/60 or 67%. TAs are still grading assn 1. Assn 2 due this Friday,
Inheritance and Polymorphism
slides created by Ethan Apter and Marty Stepp
Type Abstraction SWE Spring 2013.
CMPE212 – Reminders Assignment 2 due next Friday.
slides created by Ethan Apter
CMSC 202 Exceptions.
C++ Object Oriented 1.
Presentation transcript:

EECE 310: Software Engineering Type Hierarchies and the Substitution Principle

What have we done so far ? Defined our own Operations: Procedural abstractions Data-types: Data abstractions Loop constructs: Iteration abstraction Theme: Mechanisms that let you “extend” the language to do what you need. [See: ‘Growing a language’ by Guy Steele]

What are we going to do next ? Build our own hierarchy of types Many programming languages already do this For example, float and int are both “sub-types” of number, though they both have different implementations and semantics As another example, you can have a plain InputStream, from which you derive both BufferedInputStream and DataInputStream

Outline Basic concepts of type hierarchies Implementation of type hierarchies Liskov Substitution Principle Solutions to the LSP

Object Assignment Consider the following code: Object o = new String(“hello”); o.equals(“hello”); // OK, as Object has equals o.length(); // Error, as length is not in Object Object String Actual type Apparent type

Type Assignment Furniture f1 = new desk(“room1”, 4, 5); Furniture f2 = new chair(“room1”, 2, 3); f1.move(3, 4); // OK f2.fold(); // Error Furniture + move(x, y) Desk Chair + fold()

Type-checking Compiler checks if the apparent type of the object has a method call in its signature Does not use the actual types to check this, even if the actual type has the corresponding method Responsible for ensuring that derived types (actual types) implement all the methods in the signature of their parent (apparent) types Runtime system is responsible for actually performing the method call to the actual type

Dispatching Furniture f1 = new desk(“room1”, 4, 5); Furniture f2 = new chair(“room1”, 2, 3); f1.move(3, 4); // Which move method is called? Furniture + move(x, y) Desk Chair + fold()

Dispatching Compiler uses a dispatch vector to find method dispatch Object p Method call Method 2 Method 3

Outline Basic concepts of type hierarchies Implementation of type hierarchies Liskov Substitution Principle Solutions to the LSP

Derived Type: MaxIntSet Need a new operation called max that returns the biggest integer in the set Extends the IntSet ADT discussed earlier Implemented by keeping a separate data member called biggest in the class Returned directly by the max operation Updated by both insert and remove operations

MaxIntSet: Specification public class MaxIntSet extends IntSet { // OVERVIEW: MaxIntSet is a subtype of IntSet // with an additional method max, to find // the maximum element of the set public MaxIntSet( ); public MaxIntSet(int x); public int max( ) throws EmptyException; }

MaxIntSet: Rep and Constructor public class MaxIntSet extends IntSet { private int biggest; // the biggest element // if the set is not empty // els is inherited from the IntSet ADT public MaxIntSet() { super( ); } public MaxIntSet(int x) { super(); super.insert(x); biggest = x; }

MaxIntSet: Insert public void insert(int x) { // EFFECTS: Updates biggest to the // maximum element of the set before // inserting the new element if ( size() == 0 || x > biggest) biggest = x; super.insert( x ); }

MaxIntSet: Max public int max ( ) throws EmptyException { // EFFECTS: if this is empty throw EmptyException // else return the largest element of this if (size() == 0) { throw new EmptyException(“MaxIntSet”); } return biggest;

MaxIntSet: remove public void remove(int x) { // EFFECTS: Remove the element from the // IntSet and if it was the biggest element, // update the biggest element in the set super.remove(x); if (size() !=0 && biggest==x) updateBiggest(); // Member of MaxIntSet }

How to implement updateBiggest ? Option 1: Make the els instance variable public so that all outside code can access it Option 2: Make the els instance variable protected so that derived classes can access it Option 3: Use the existing public interface of IntSet, but use its iterator method

MaxIntSet: Rep. Invariants (Option 3) Rep Invariant only needs to define the constraints on the biggest variable Everything else is inherited from IntSet Cannot change this as it only has public access IMaxIntSet( c ) = (c.size == 0) || ( (c.biggest in AFIntSet(c) ) && (for all x in AFIntSet(c), x <= c.biggest) )

MaxIntSet: Rep. Invariants (Option 2) Rep Invariant needs to define the constraints on both the biggest and els variables Since it can potentially modify the els variable May use the RI of the IntSet ADT in its definition IMaxIntSet( c ) = IIntSet( c ) && ( (c.size == 0) || ( (c.biggest in AFIntSet(c) ) && (for all x in AFIntSet(c), x <= c.biggest) ) )

In class exercise What if the IntSet ADT maintained its els vector in sorted order (smallest to biggest). How would you implement the max operation in its sub-type MaxIntSet ? Using protected access ? Through its public Interface ? Explain the rationale for your choice and show the implementation using your preferred method.

Outline Basic concepts of type hierarchies Implementation of type hierarchies Liskov Substitution Principle Solutions to the LSP

NonEmptySet Type Consider a subtype of IntSet called non-empty set, with the stipulation that it must *never* be empty. i.e., it has at least 1 element always Constructor takes the element as an argument and adds it to the els vector (the rep) insert, size, isIn work as before (no change) remove must make sure it never leaves the set empty, otherwise it throws an EmptySetException

NonEmptySet: Remove public class NonEmptySet extends IntSet { … public void remove(int x) throws EmptySetException { // EFFECTS: If set has at least two elements, // then remove x from the set // Otherwise, throw the EmptySetException …. }

RemoveAny procedure public static Boolean removeAny(IntSet s) { // EFFECTS: Remove an arbitrary element from // the IntSet if the set is not empty, return true // Otherwise do nothing and return false if (s.size() == 0) return false; int x = s.choose(); s.remove(x); return true; }

Usage of removeAny IntSet s = new IntSet(); … // Add elements to s while ( removeAny(s) ) { } // s is empty at this point

What about this one ? IntSet s = new NonEmptySet(3); … // Add elements to s while ( removeAny(s) ) { } // control never reaches here ! Can potentially throw an EmptySet exception !

So what’s the problem ? Subtype defines new behavior that is not compatible with the parent type Users of parent type should be correct even if they do not know about the sub-type (locality) Sub-type objects can be substituted with the parent type objects without causing existing code that uses the parent type to break (modifiability)

Liskov Substitution principle Intuition Users can use and reason about subtypes just using the supertype specification. Definition Subtype specification must support reasoning based on the super-type specification according to following rules: signature rule methods rule properties rule 1. The code should behave the same way a supertype would have even when subtype is used

Signature Rule Every call that is type-correct with the super-type objects must also be type-correct with the sub-type objects Sub-type objects must have all the methods of the super-type Signatures of the subtype’s implementations must be compatible with the signatures of the corresponding super-type methods

Signature Rule in Java Subtype’s method can have fewer exceptions but NOT throw more exceptions Arguments and return type should be identical: (stricter than necessary) Foo clone(); Foo x = y.clone(); Object clone(); Foo x = (Foo) y.clone(); Enforced by the compiler at compile-time Subtypes can have fewer exceptions Enforced by the compiler

NonEmptySet: Remove public class NonEmptySet extends IntSet { … public void remove(int x) throws EmptySetException { // EFFECTS: If set has at least two elements, // then remove x // Otherwise, throw the EmptySetException …. } Violates signature rule – will not compile

Will this solve the problem ? public class NonEmptySet extends IntSet { … public void remove(int x) { // EFFECTS: If set has at least two elements, // then remove x // Otherwise, do nothing …. }

What will happen in this case ? IntSet s = new NonEmptySet(3); … // Add elements to s while ( removeAny(s) ) { } // control never reaches here ! Will loop forever because the set never becomes empty (why ?)

What’s the problem here ? The remove method of NonEmptyIntSet has a different behavior than the remove method of the IntSet ADT (it’s parent type) In the IntSet ADT, after you call remove(x), you are assured that x is no longer part of the set (provided the set was non-empty prior to the call) In the NonEmptyIntSet ADT, after you call remove(x), you do not have this assurance anymore which violates the substitution principle

Methods rule A sub-type method can weaken the pre-condition (REQUIRES) of the parent method and strengthen its post-condition (EFFECTS) Pre-condition rule: presuper=> presub Post-condition rule: presuper && postsub => postsuper Both conditions must be satisfied to achieve compatibility between the sub-type and super-type methods

Remember … Weakening of pre-condition: REQUIRES less Example: Parent-type requires a non-empty collection, but the sub-type does not Example: Parent-type requires a value > 0, sub-type can take a value >=0 in its required clause Strengthening of post-condition: DOES more Example: Sub-type returns the elements of the set in sorted order while parent-type returns them in any arbitrary order (sorted => arbitrary)

Example of methods rule Consider a sub-type of IntSet LogIntSet which keeps track of all elements that were ever in the set even after they are removed public void insert(int x) // MODIFIES: this // EFFECTS: Adds x to the set and to the log Does this satisfy the methods rule ?

Is the methods rule satisfied here ? Consider another sub-type PositiveIntSet which only adds positive Integers to the set public void insert(int x) // MODIFIED: this // EFFECTS: if x >= 0 adds it to this // else does nothing

Back to the NonEmptySet Type public class NonEmptySet { // Not derived from IntSet // A Non-empty IntSet is a mutable set of integers // whose size is at least 1 always public void removeNonEmpty(int x) { // EFFECTS: If set has at least two elements, // then remove x // Otherwise, do nothing …. }

Regular IntSet public class IntSet extends NonEmptySet { // Overview: A regular IntSet as before public void remove(int x) { // MODIFIES: this // EFFECTS: Removes x from this … }

What happens in this code ? public static void findMax (NonEmptySet s) { int max = s.choose(); iterator g = s.elements(); while (g.hasNext() ) { … } Can throw an exception if IntSet is passed in as argument

What’s the problem here ? The IntSet type has an operation remove which causes it to violate the invariant property of its parent type NonEmptySet Calling code may be able to make the set empty by calling remove and then pass it to findMax Not enough if the derived methods preserve the parent-type’s invariant, the new methods in sub-type must do so as well

Properties Rule Subtype must preserve each property of the super-type in each of its methods Invariant properties (always true) Evolution properties (evolve over time) Examples Invariant property: The set never becomes empty Evolution property: The set size never decreases

Putting it together: Substitution Principle Signature rule: If program is type-correct based on super-type specification, it is also type-correct with respect to the sub-type specification. Methods rule: Ensures that reasoning about calls of super-type methods is valid even if the call goes to code that implements a subtype. Properties rule: Reasoning about properties of objects based on super-type specification is still valid even when objects belong to the sub-type.

In-class exercise public class Counter { // Overview: Counter should never decrease public Counter( ); // EFFECTS: Makes this contain 0 public int get( ); // EFFECTS: Returns the value of this public void incr(); // MODIFIES: this // EFFECTS: Increases the value of this

In class exercise (contd..) Now consider a type Counter2 with the following methods. Can this be a valid sub-type of Counter? public Counter2( ); // EFFECTS: Makes this contain 0 public void incr( ); // MODIFIES: this // EFFECTS: Makes this contain twice its value

In class exercise (contd..) Signature Rule: Satisfied by counter2 ? Methods rule: Satisfied by counter2 ? Pre-condition rule: presuper=> presub Post-condition rule: presuper && postsub => postsuper Properties rule: Satisfied by counter2 ? Invariant property ? Evolution property ?

Summary of LSP Liskov Substitution Principle (LSP) is a unifying way of reasoning about the use of sub-types Signature rule: Syntactic constraint and can be enforced by compiler Methods rule and properties rule: Pertain to semantics (behavior) and must be enforced by programmer LSP is essential for locality and modifiability of programs using types and sub-types

Outline Basic concepts of type hierarchies Implementation of type hierarchies Liskov Substitution Principle Solutions to the LSP

Why do we use sub-types ? Define relationships among a group of types SortedList and UnsortedList are sub-types of List Specification reuse (common interface) Using code simply says “give me a list” Implementation reuse (code sharing) SortedList need not re-implement all of List’s methods Modifiability of parent type Client need not change if parent class implementation changes (if done through public interface)

Why not to use sub-types ? Sub-types are not appropriate in many cases Sub-type must satisfy Liskov Substitution Principle. In other words, it must not cause existing code to break. Subtype’s implementation must not depend on the implementation details of the parent type Common rule of thumb: “Sub-types must model is a special kind of relationship” Not always as simple as we will soon see

InstrumentedIntSet Consider an example InstrumentedIntSet which keeps track of the number of elements ever added to the IntSet ADT (different from its size). Should this type inherit from IntSet ? Must add a new field to keep track of count Override the add method to increment count Override the addAll method to increment count

InstrumentedIntSet: Inheritance public class InstrumentedIntSet extends IntSet { private int addCount = 0; // The number of attempted element insertions public InstrumentedIntSet() { super(); } public boolean add(Object o) { addCount++; return super.add(o); } public boolean addAll(Collection c) { addCount += c.size(); return super.addAll(c); public int getAddCount() { return addCount;

What’s the problem here ? Consider the following code: IntSet s = new InstrumentedIntSet(); // Assume that array a has 3 int elements s.addAll( a ); int i = s.getAddCount( ); // Returns 6 !!! How will you fix this problem ? 1. Modify addAll to not do the increment, but what if base class does not call add (implementation specific) 2. Write your own version of addAll in the derived class to do the iteration (no reuse, duplication)

Solution: Use Composition Instead of making InstrumentedIntSet a sub-type of IntSet, make it contain an IntSet In Java, it holds a reference to an IntSet rather than a copy, so be careful to not expose it Do not have to worry about substitution principle (though that is not a problem in this example) Make both classes implement a common Set interface if you want to use one in place of another (why not use abstract base class ?).

InstrumentedIntSet: Composition-1 public class InstrumentedIntSet implements Set { private IntSet s; private int addCount; public InstrumentedIntSet( ) { addCount = 0; s = new IntSet(); }

InstrumentedIntSet: Composition-2 public class InstrumentedIntSet implements Set { public void add(int element) { addCount = addCount + 1; s.add(element); } public void addAll(Collection c) { addCount = addCount + c.size(); s.addAll( c );

Inheritance Vs. Composition Every A-object is a B-object. –Calling A-object’s methods automatically executes B’s code, unless overridden (implicit code reuse). –Call to overridden method from inherited method executes A’s version. –A’s method code can see B’s protected internals. Every A-object has a B-object. –A must implement all methods, but may delegate actual work to the internal B-object (explicit code-reuse). –Call to “non-delegated” method from delegated method runs B’s version. –B’s internals hidden from A –Interface may help if you want to substitute one for another

Should B be a subtype of A ? Do we need to use B in place of A ? Start NO YES Does B satisfy the LSP ? Do B and A need to share any code ? NO NO YES YES Make B and A independent types (common interface if necessary) Make B a sub-type of A, but try to use the public interface of A in B if possible and efficient Make B contain an object of type A (common interface if necessary)

Summary of Sub-typing Inheritance is often over-used without regard for its consequences (e.g., Java class library) Not always straightforward to ensure behavioral substitutability of parent-type with sub-type Subtle dependencies of sub-type on parent type’s implementation details can cause fragility Use composition instead of inheritance whenever possible (with interfaces if necessary)