Liskov Substitution Principle

Similar documents
Object Oriented Software Design - I

Open Closed Principle (OCP)

Single Responsibility Principle (SRP)

S.O.L.I.D: Software Engineering Principles

Design Principles: Part 2

Design Principles: Part 2

Outline. Design Principles: Part 2. e.g. Rectangles and Squares. The Liskov Substitution Principle (LSP) ENGI 5895: Software Design.

CS 520 Theory and Practice of Software Engineering Fall 2017

CS 520 Theory and Practice of Software Engineering Fall 2018

OO Class Design Principles

Principles of OO Design

11/2/09. Code Critique. What goal are we designing to? What is the typical fix for code smells? Refactoring Liskov Substitution Principle

Software Engineering CSC40232: SOFTWARE ENGINEERING. Guest Lecturer: Jin Guo SOLID Principles sarec.nd.edu/courses/se2017

Last Time: Object Design. Comp435 Object-Oriented Design. Last Time: Responsibilities. Last Time: Creator. Last Time: The 9 GRASP Patterns

Test Code Patterns. How to design your test code

Testing and Inheritance. Test Code Patterns. Inheritance-related bugs. Inheritance. Testing of Inheritance. Inheritance-related bugs

CS 320 Introduction to Software Engineering Spring March 06, 2017

Outline. Subtype Polymorphism, Subtyping vs. Subclassing, Liskov Substitution Principle. Benefits of Subtype Polymorphism. Subtype Polymorphism

Object-oriented design principles

Principles of Object-Oriented Design

CS 320 Introduction to Software Engineering Spring March

11/4/15. Review. Objec&ves. Refactoring for Readability. Review. Liskov Subs&tu&on Principle (LSP) LISKOV SUBSTITUTION PRINCIPLE

Lecture: Modular Design

Chapter 6: Inheritance

n HW5 out, due Tuesday October 30 th n Part 1: Questions on material we ll cover today n Part 2: BFS using your graph from HW4

Software Engineering

SOLID Principles. Equuleus Technologies. Optional Subheading October 19, 2016

Software Engineering Design & Construction

Bruno Bossola SOLID Design Principles

17.11 Bean Rules persistent

Objec&ves. Review. Liskov Subs&tu&on Principle Refactoring for Extensibility. What are reasons that we refactor our code?

Class Design Principles

Intro to: Design Principles

Introduction to C++ Introduction to C++ Dr Alex Martin 2013 Slide 1

COURSE 2 DESIGN PATTERNS

Software Engineering Design & Construction Dr. Michael Eichberg Fachgebiet Softwaretechnik Technische Universität Darmstadt

The Liskov Substitution Principle

CPSC 410? Advanced Software Engineering Mid-term Examination (Term I ) SOLUTION Instructor: Gail Murphy

Object-Oriented Concepts and Design Principles

Introduction to Object-Oriented Programming

S.O.L.I.D. Principles of

Conception Orientée Objets. Programmation SOLID

Produced by. Agile Software Development. Eamonn de Leastar

Software Engineering Design & Construction Dr. Michael Eichberg Fachgebiet Softwaretechnik Technische Universität Darmstadt

Java Control Statements

The S.O.L.I.D. Principles. of Object Oriented Programming

Polymorphism: Interfaces and Iteration. Fundamentals of Computer Science

Software Engineering /48

SOLID: Principles of OOD

Inheritance and Substitution (Budd chapter 8, 10)

CMPS 115 Winter 04. Class #10 (2004/02/05) Changes/Review Programming Paradigms Principles of OOD <break> Design Patterns

OO Design Principles

CSC207 Week 3. Larry Zhang

Java Object Oriented Design. CSC207 Fall 2014

1B1b Inheritance. Inheritance. Agenda. Subclass and Superclass. Superclass. Generalisation & Specialisation. Shapes and Squares. 1B1b Lecture Slides

Ingegneria del Software Corso di Laurea in Informatica per il Management. Software quality and Object Oriented Principles

Inheritance and object compatibility

C++ Modern and Lucid C++ for Professional Programmers

Software Paradigms (Lesson 3) Object-Oriented Paradigm (2)

Inheritance in Java. Produced by: Eamonn de Leastar Dr. Siobhán Drohan

Programming in the large

Software Design and SOLID Principles

OOD Smells and Principles

CSC207H: Software Design SOLID. CSC207 Winter 2018

Single Responsibility Principle

Assertions. Assertions - Example

M301: Software Systems & their Development. Unit 4: Inheritance, Composition and Polymorphism

Java Overview An introduction to the Java Programming Language

Plan. Design principles: laughing in the face of change. What kind of change? What are we trying to achieve?

Basic design patterns

Summary of the course lectures

Software Engineering I (02161)

Interfaces and itera-on. CSCI 136: Fundamentals of Computer Science II Keith Vertanen

ECE 3574: Dynamic Polymorphism using Inheritance

Outline. Inheritance. Abstract Classes Interfaces. Class Extension Overriding Methods Inheritance and Constructors Polymorphism.

Object-oriented Programming. Object-oriented Programming

Self-review Questions

Inheritance, and Polymorphism.

Polymorphism and Interfaces. CGS 3416 Spring 2018

Object-oriented programming. and data-structures CS/ENGRD 2110 SUMMER 2018

Component-Level Design

Inheritance Systems. Merchandise. Television Camcorder Shirt Shoe Dress 9.1.1

Lecture 11 Subtypes and Subclasses

CSE 331 Software Design and Implementation. Lecture 12 Subtypes and Subclasses

[ L5P1] Object-Oriented Programming: Advanced Concepts

Annotations in Java (JUnit)

Highlights of Previous Lecture

CS/ENGRD 2110 FALL Lecture 7: Interfaces and Abstract Classes

Inheritance and Polymorphism

Today's Agenda. References. Open Closed Principle. CS 247: Software Engineering Principles. Object-Oriented Design Principles

Inheritance and Polymorphism

Adapter Pattern Structural

1 Introduction 2 Complex Systems 3 Object Model 4 Dependency Management 5 Class Design. Object Oriented Programming in Physics

OBJECT ORİENTATİON ENCAPSULATİON

Object-Oriented Design

Produced by. Design Patterns. MSc in Communications Software. Eamonn de Leastar

Originality is Overrated: OO Design Principles

CS-202 Introduction to Object Oriented Programming

Inheritance. CSE 142, Summer 2002 Computer Programming 1.

References: internet notes; Bertrand Meyer, Object-Oriented Software Construction; 10/14/2004 1

Transcription:

Liskov Substitution Principle Produced by: Eamonn de Leastar (edeleastar@wit.ie) Dr. Siobhán Drohan (sdrohan@wit.ie) Department of Computing and Mathematics http://www.wit.ie/

SOLID Class Design Principles Module Scope S O L I D Single Responsibility Principle (SRP). Classes should have one, and only one, reason to change. Keep your classes small and single-purposed. Open-Closed Principle (OCP). Design classes to be open for extension but closed for modification; you should be able to extend a class without modifying it. Minimize the need to make changes to existing classes. Liskov Substitution Principle (LSP). Subtypes should be substitutable for their base types. From a client s perspective, override methods shouldn t break functionality. Interface Segregation Principle (ISP). Clients should not be forced to depend on methods they don t use. Split a larger interface into a number of smaller interfaces. Dependency Inversion Principle (DIP). High-level modules should not depend on low-level modules; both should depend on abstractions. Abstractions should not depend on details; details should depend on abstractions.

Barbara Liskov 4

LSP Methods that refer to base classes must be able to use objects of derived types without knowing it. If for each object o 1 of type S there is an object o 2 of type T such that for all programs P defined in terms of T, the behaviour of P is unchanged when o 1 is substituted for o 2 then S is a subtype of T. Barbara Liskov, Data Abstraction and Hierarchy, SIGPLAN Notices, 23,5 (May, 1988). T S extends O 2 : T O 1 : S 5

LSP: Simple Violation (and fix)

Simple Violation of LSP references a base type Shape violates LSP because it must know of every derived type of Shape. void drawshapes (Shape shape) if (shape instanceof Square) drawsquare ((Square)shape); else if (shape instance of Circle) drawcircle ((Circle) shape); drawshapes must be modified whenever new derivatives of Shape are presented. What other SOLID principle does it violate? 7

Adhering to LSP class Shape void draw() // class Circle extends Shape private double itsradius; private Point itscenter; public void draw() // class Square extends Shape private double itsside; private Point itstopleft; public void draw() // void drawshape (Shape s) s.draw(); drawshape now adheres to LSP 8

LSP: Semantic Violation

http://www.codemag.com/article/1001061 LSP An object inheriting from a base class, interface, or other abstraction must be semantically substitutable for the original abstraction.

Rectangle class Rectangle private int width; private int height; Rectangle public void setwidth (int width)... public void setheight (int height)... public int getwidth ()... public int getheight ()... Rectangle class is released for general use. 11

Square Introduce Square as a subclass of Rectangle. Rectangle Inheritance is a relationship: A Square is a rectangle. However, there is a subtle difference it s width and height are equal: Square Square only needs one dimension but both are inherited. 12

Square For a Square, both setwidth() and setheight() should not vary independently. Rectangle Client could easily call one and not the other thus compromising the Square. class Rectangle private int width; private int height; Square public void setwidth (int width)... public void setheight (int height)... public int getwidth ()... public int getheight ()...

Square Potential solution: implement setwidth() and setheight() in Square class. Each of these methods should then make sure both width & height are adjusted. class Rectangle private int width; private int height; Rectangle Square public void setwidth (int width)... public void setheight (int height)... public int getwidth ()... public int getheight ()...

Square Potential solution implementation: class Square extends Rectangle public void setwidth (int width) super.setwidth(width); super.setheight(width); public void setheight (int height) super.setwidth(height); super.setheight(height); Rectangle Square 15

Polymorphism void f (Rectangle r) r.setwidth(5); Rectangle Polymorphism ensures, if the f() method: is passed a Rectangle, then its width will be adjusted. is passed a Square, then both height and width will be changed Square Assume model is consistent & correct. However. 16

More Subtle Problem void g (Rectangle r) r.setwidth(5); r.setheight(4); assert (r.getwidth() * r.getheight()) == 20; If r is a Rectangle instance g() methods works as expected If r is a Square instance g() assertion fails g() assumes that width and height of a Rectangle can be varied independently. Substitution of a Square violates this assumption. Square violates LSP. 17

LSP: Semantic Violation Square!= Rectangle

LSP: Subtypes should be substitutable for their base types.

Validating the Model A model, viewed in isolation, cannot be meaningfully validated. Rectangle The validity of a model can only be expressed in terms of its clients: Examining the final version of the Square and Rectangle classes in isolation, we found that they were self consistent and valid. Square When we examined from the viewpoint of g() (which made reasonable assumptions) the model broke down. 20

Validating the Model When considering whether a design is appropriate or not, it Rectangle must be examined in terms of the reasonable assumptions that will be made by the users of that design. Square 21

Behavioural Problems A square might be a rectangle, but a Square object is not a Rectangle object. the behaviour of a Square object is not consistent with the behaviour of a Rectangle object. The LSP makes clear that inheritance relationship pertains to behaviour that clients depend upon. Square!= Rectangle

With LSP is-a really means behaves exactly like

Fragile Base Class Problem A common problem with Implementation Inheritance

Fragile Base Class Problem class Stack extends ArrayList private int stack_pointer = 0; public void push( Object article ) add( stack_pointer++, article ); public Object pop() return remove( --stack_pointer ); public void push_many( Object[] articles ) for( int i = 0; i < articles.length; ++i ) push( articles[i] ); 25

Fragile Base Class Problem class Stack extends ArrayList private int stack_pointer = 0; public void push( Object article ) add( stack_pointer++, article ); public Object pop() return remove( --stack_pointer ); public void push_many( Object[] articles ) for( int i = 0; i < articles.length; ++i ) push( articles[i] ); Stack a_stack = new Stack(); a_stack.push("1"); a_stack.push("2"); a_stack.clear(); Uses the ArrayList's clear() method to pop everything off the stack 26

Fragile Base Class Problem class Stack extends ArrayList private int stack_pointer = 0; public void push( Object article ) add( stack_pointer++, article ); public Object pop() return remove( --stack_pointer ); public void push_many( Object[] articles ) for( int i = 0; i < articles.length; ++i ) push( articles[i] ); Stack a_stack = new Stack(); a_stack.push("1"); a_stack.push("2"); a_stack.clear(); Code successfully executes, but since the base class doesn't know anything about the stack pointer, the Stack object is now in an undefined state. 27

Fragile Base Class Problem class Stack extends ArrayList private int stack_pointer = 0; public void push( Object article ) add( stack_pointer++, article ); public Object pop() return remove( --stack_pointer ); public void push_many( Object[] articles ) for( int i = 0; i < articles.length; ++i ) push( articles[i] ); Stack a_stack = new Stack(); a_stack.push("1"); a_stack.push("2"); a_stack.clear(); The next call to push() puts the new item at index 2 (the stack_pointer's current value), so the stack effectively has three elements on it the bottom two are garbage. 28

Use Composition instead of Inheritance Inheritance is an is-a relationship. Composition is a has-a relationship. 29

Composed Solution class Stack private int stack_pointer = 0; private ArrayList the_data = new ArrayList(); public void push( Object article ) the_data.add( stack_pointer++, article ); public Object pop() return the_data.remove( --stack_pointer ); public void push_many( Object[] articles ) for( int i = 0; i < o.length; ++i ) push( articles[i] ); 30

There s no clear() method now (so far so good). BUT let s extend the behaviour

Monitorable Stack class Monitorable_stack extends Stack private int high_water_mark = 0; private int current_size; @Override public void push( Object article ) if( ++current_size > high_water_mark ) high_water_mark = current_size; super.push(article); Tracks the maximum stack size over a certain time period. @Override public Object pop() --current_size; return super.pop(); public int maximum_size_so_far() return high_water_mark; 32

push_many Implementation void f(stack s) //... s.push_many (someobjectarray); //... Which class implements push_many() method? class Stack private int stack_pointer = 0; private ArrayList the_data = new ArrayList(); class Monitorable_stack extends Stack private int high_water_mark = 0; private int current_size; public void push( Object article ) the_data.add( stack_pointer++, article ); public Object pop() return the_data.remove( --stack_pointer ); public void push_many( Object[] articles ) for( int i = 0; i < o.length; ++i ) push( articles[i] ); @Override public void push( Object article ) if( ++current_size > high_water_mark ) high_water_mark = current_size; super.push(article); @Override public Object pop() --current_size; return super.pop(); public int maximum_size_so_far() return high_water_mark; 33

push_many Implementation void f(stack s) //... s.push_many (someobjectarray); //... If f()is passed a MonitorableStack, does a call to push_many()update high_water_mark? class Stack private int stack_pointer = 0; private ArrayList the_data = new ArrayList(); class Monitorable_stack extends Stack private int high_water_mark = 0; private int current_size; public void push( Object article ) the_data.add( stack_pointer++, article ); public Object pop() return the_data.remove( --stack_pointer ); public void push_many( Object[] articles ) for( int i = 0; i < o.length; ++i ) push( articles[i] ); @Override public void push( Object article ) if( ++current_size > high_water_mark ) high_water_mark = current_size; super.push(article); @Override public Object pop() --current_size; return super.pop(); public int maximum_size_so_far() return high_water_mark; 34

push_many Implementation void f(stack s) //... s.push_many (someobjectarray); //... class Stack private int stack_pointer = 0; private ArrayList the_data = new ArrayList(); Polymorphism ensures that MonitrableStack s push() method is called, and high_water_mark is appropriately updated. class Monitorable_stack extends Stack private int high_water_mark = 0; private int current_size; public void push( Object article ) the_data.add( stack_pointer++, article ); public Object pop() return the_data.remove( --stack_pointer ); public void push_many( Object[] articles ) for( int i = 0; i < o.length; ++i ) push( articles[i] ); @Override public void push( Object article ) if( ++current_size > high_water_mark ) high_water_mark = current_size; super.push(article); @Override public Object pop() --current_size; return super.pop(); public int maximum_size_so_far() return high_water_mark; 35

push_many Implementation void f(stack s) //... s.push_many (someobjectarray); //... class Stack private int stack_pointer = 0; private ArrayList the_data = new ArrayList(); This is because Stack.push_many()calls the push()method, which is overridden by MonitorableStack class Monitorable_stack extends Stack private int high_water_mark = 0; private int current_size; public void push( Object article ) the_data.add( stack_pointer++, article ); public Object pop() return the_data.remove( --stack_pointer ); public void push_many( Object[] articles ) for( int i = 0; i < o.length; ++i ) push( articles[i] ); @Override public void push( Object article ) if( ++current_size > high_water_mark ) high_water_mark = current_size; super.push(article); @Override public Object pop() --current_size; return super.pop(); public int maximum_size_so_far() return high_water_mark; 36

Revised Stack A profiler is run against an implementation using Stack. It notices the Stack isn't as fast as it could be and is heavily used. The base class Stack is improved i.e. rewritten so it doesn't use an ArrayList and consequently it gains a performance boost 37

Revised Stack using Arrays class Stack private int stack_pointer = -1; private Object[] stack = new Object[1000]; No longer calls push(); public void push( Object article ) assert stack_pointer < stack.length; stack[ ++stack_pointer ] = article; public Object pop() assert stack_pointer >= 0; return stack[ stack_pointer-- ]; public void push_many( Object[] articles ) assert (stack_pointer + articles.length) < stack.length; System.arraycopy(articles, 0, stack, stack_pointer+1, articles.length); stack_pointer += articles.length; 38

Problems? void f(stack s) //... s.push_many (someobjectarray); //... If s is a MonitorableStack, is high_water_mark updated? No because the new Stack base class push_many() implementation does not call push()at all LSP Violation: i.e. function f()will not appropriately operate a Stack derived object. 39

Solution to our Fragile Base Class interface Stack void push( Object o ); Object pop(); void push_many( Object[] source ); MonitorableStack now USES a SimpleStack; it IS NOT a SimpleStack 40

Delegation / Inheritance Pattern Create an interface, not a class. Instead of implementing methods at base-class level, instead, provide a default implementation of those methods. Instead of extending the baseclass, implement the interface. Then, for every interface method, delegate to a contained instance of the default implementation. interface Stack void push( Object o ); Object pop(); void push_many( Object[] source ); class Simple_Stack implements Stack // code omitted class Monitorable_Stack implements Stac private int high_water_mark = 0; private int current_size; Simple_stack stack = new Simple_stack public void push( Object o ) if( ++current_size > high_water_mar high_water_mark = current_size; stack.push(o); //code omitted

Simple_Stack class Simple_Stack implements Stack private int stack_pointer = -1; private Object[] stack = new Object[1000]; public void push( Object article ) assert stack_pointer < stack.length; stack[ ++stack_pointer ] = article; public Object pop() assert stack_pointer >= 0; return stack[ stack_pointer-- ]; public void push_many( Object[] articles ) assert (stack_pointer + articles.length) < stack.length; System.arraycopy(articles, 0, stack, stack_pointer+1, articles.length); stack_pointer += articles.length; 42

class Monitorable_Stack implements Stack private int high_water_mark = 0; private int current_size; Simple_stack stack = new Simple_stack(); public void push( Object o ) if( ++current_size > high_water_mark ) high_water_mark = current_size; stack.push(o); We delegate to SimpleStack which could be a base class but isn t. public Object pop() --current_size; return stack.pop(); public void push_many( Object[] source ) if( current_size + source.length > high_water_mark ) high_water_mark = current_size + source.length; stack.push_many( source ); public int maximum_size() return high_water_mark; We are forced to implement push_many which also delegates to 43 SimpleStack.

Holub s Advice In general, it's best to avoid concrete base classes and extends relationships in favour of interfaces and implements relationships. Rule of thumb : 80 percent of code at minimum should be written entirely in terms of interfaces. e.g. never use references to a HashMap, use references to the Map The more abstraction you add, the greater the flexibility. In today's business environment, where requirements regularly change as the program develops, this flexibility is essential. 44

Except where otherwise noted, this content is licensed under a Creative Commons Attribution- NonCommercial 3.0 License. For more information, please see http://creativecommons.org/licenses/by-nc/3.0/