A general discussion on the accepted design and principles of Object Oriented programming
Wednesday, December 18, 2013
Monday, December 16, 2013
Single Responsibility Principle
Single Responsibility Principle ( SRP ), according to Robert C. Martin, states:
A class should have one and only one reason to change
This principle highlights the criterion and factors governing the ways to define the responsibilities of a class.
This principle has to be understood, together with Open Closed Principle and Interface Segregation Principle, both of which are concerned with factors governing high cohesion, low coupling and ease of readability and maintenance.
Class BankAccount cluttered with too many differential responsibilites.
Let's redesign the BankAccount class of Example # 1. The redesigned structure is illustrated, above, by figure:4.
Responsibilities of BankAccount are broken into three separate responsibilities, each defined by a separate interface.
Interface to handle responsibilities of Bank Account Transaction
Interface to handle Transaction History
Interface to handle Call centre related activities
A class should have one and only one reason to change
This principle highlights the criterion and factors governing the ways to define the responsibilities of a class.
This principle has to be understood, together with Open Closed Principle and Interface Segregation Principle, both of which are concerned with factors governing high cohesion, low coupling and ease of readability and maintenance.
What does a program achieve on conformation to SRP
High cohesion between the elements of a class and better segregation of concerns, which promotes readability and maintainability.
High Cohesion is promoted because a class's responsibilities are precisely defined in a way that the class changes exactly for one reason. This, of course, does not mean that a class should have only one public method ( corresponding to one behavior ). It just means that the different public methods must be closely aligned to each other in behavior. 'Closely aligned' is a qualitative term and various behavioral and structural concerns must be analysed to figure out it's workable meaning under a given context.
SRP also promotes Segregation of concerns, factors governing segregation of concerns are similar to the the factors governing Interface Segregation Principle, because a class is not allowed to expose any undue public methods to the clients. It might be possible, under a given design structure, that the methods exposed by a class are not required in totality by a client. Some clients may be solely concerned with a subset of the methods while some other clients concerned with a totally different subset. With the classes design to have a single responsibility , such exposure of undue methods to clients tend to a minimum
Encapsulation and Responsibility
A class is data and behavior combined into one unit. In order that changes to a class do cascade down to the other interacting classes in an undesirable way, it is necessary that a class exposes only a minimum limited information about itself to other classes. The more information a class keeps to itself, lesser will be the impact of the change on other interacting classes. So class's public member variables and methods come under strict scrutiny. As a rule, public member variables are almost always avoided. Public methods, which are a reflection of the responsibilities that a class exposes to other interacting classes, should take care of some design considerations, which I shall take in the sections below.How To Find If A Given Object Structure Violates SRP
This section analyses few design considerations for building classes with single responsibility and also explains design structures that are optimal candidates for SRP violation.
Split Responsibility ( Cluttered Responsibility )
Lets consider a class whose responsibilities are defined by N number of public methods. It is possible to group these methods into K behavioral groups, where groups represent disjoint sets of methods.
This is illustrated in the diagram, below.
figure:1
This is illustrated in the diagram, below.
For a class violating SRP it is possible to arrange the relationship-dependencies of the behavioral sets as:
- Relationship between methods of a given behavior are 'closely' related. For 'closely' related methods it is highly likely that a client using one of the methods of a given behavioral set will also require the use of other methods of the same set. The methods of a given behavioral set are complementary to each other. For example, think of methods like start(), code() , test(), stop(). They are very likely to be used together and are complementary to each other. They constitute methods of one behavior. Methods like attendMeeting(), takeNotes() are also complementary and highly likely to be used together, and hence, constitute methods of different behavioral set. But methods attendMeeting() is not closely related to code() or stop(). A class whose responsibilities are defined by all the methods, namely start(), code(), test(), stop(), attendMeeting(), takeNotes() , may be a recipient of not obeying single responsibility.
- Inter-Relationship between methods of different behavioral set are 'not closely' related, i.e it is highly unlikely that a client would use methods of different behavioral set in one class. This simply means that a class's different behavioral set has a tendency to get coupled with other interacting classes in a non-unitary and differential way. This means changes to a given class has a tendency to propagate in different directions. For example, a change of requirement 1 will propagate to module 1 , whereas a change of requirement 2 will propagate to module 2 and so on.
Classes obeying the above 1) and 2) can be said to have a 'split responsibility'. As can be seen in figure:1, class Split_Class has differential responsibilities assigned. This class change for a variety of reasons, either change in Behavior_12 or Behavior_AB or Behavior_XY, each change propagating to a different module. Add to this, these behaviors themselves depend upon other modules. It's maintainability, re-usability and extensibility are highly questionable. It is an ideal recipient to be re-designed with SRP.
Unit Responsibility
Classes with a single responsibility which present high cohesion and are, thus, easier to use and maintain
figure:2
Figure:2 illustrates a class with single responsibility.
Code Examples
Example # 1
This Example consists of Bank Account system , illustrated, below, by figure:3
figure:3
The class BankAccount handles varying responsibilities:
- One responsibility consists of withdrawal and fetch account balance.
- Other responsibility consists of fetch Transaction History and,
- Yet another responsibility, unrelated with the first two, consists of registering complaints and fetching complaint status
Other modules of the system depend upon Bank Account class but use only a selected behaviors of Bank Account. For example, Bank Call Centre module uses only the methods for registering complaints and fetching complaint status, whereas ATM modules uses only the methods for withdrawal and fetching account balance.
BankAccount class can not only change for multiple reasons but also has a insidious coupling with other modules. This class, indeed, violates SRP.
/** * This class handles various unrelated responsibilities, has poor cohesion * and violates SRP */ public class BankAccount { public Status withdraw( long amount ) { // withdraws the given amount and returns the transation success/failure status } public long getAccountBalance() { // returns Account balance } public TransactionHistory getTransactionHistory( int lastXDays ) { // returns a transaction history of last 'X' days } public int registserComplaint( ComplaintDetails complaint ) { // returns complaintId } public Status getComplaintProgressStatus( long complaintId ) { // Returns the Status the progress of the given Complain ID } }
How To Avoid SRP Violation
The class with split responsibility needs to separated into multiple classes, each adhering to a single responsibility. Coupling of the separated classes with other interacting classes should be achieved via a layer of abstraction.
figure:4
Let's redesign the BankAccount class of Example # 1. The redesigned structure is illustrated, above, by figure:4.
Responsibilities of BankAccount are broken into three separate responsibilities, each defined by a separate interface.
interface BankAccount { Status withdraw(long amount); long getAccountBalance(); }
class BankAccountImpl implements BankAccount { public Status withdraw(long amount) { // withdraws the given amount and returns the transation success/failure // status } public long getAccountBalance() { // returns Account balance } }
Interface to handle Transaction History
interface BankTransactionHistory { TransactionHistory getTransactionHistory(int lastXDays); }
class BankTransactionHistoryImpl implements BankTransactionHistory { public TransactionHistory getTransactionHistory(int lastXDays) { // returns a transaction history of last 'X' days } }
Interface to handle Call centre related activities
interface BankHelpCentre { int registserComplaint(Complaint complaint); Status getComplaintProgressStatus(long complaintId); }
class BankHelpCentreImpl implements BankHelpCentre { public int registserComplaint(Complaint complaint) { // returns complaintId } public Status getComplaintProgressStatus(long complaintId) { // Returns the Status the progress of the given Complain ID } }
S O L I D
Wednesday, December 11, 2013
Open Closed Principle
The Open Closed Principle ( OCP ) states:
Software Entities ( Classes, Methods, etc ) should be open for extension but closed for modification
A requirement of change in a class, many a times, cascades into spiraling changes across other classes and other dependent modules. Such a fragile, rigid and non-extinsible program puts a tremendous constraint on the incremental development as well as code maintenance.
Open closed principle, precisely , addresses this problem. It advocates that a program once tested and in production, should no longer be open to any further changes. A program is not open to any change, except on error conditions. Any change in the requirements should be incorporated only and only by extending the class through inheritance.
So far so good.
But how good this system fares with respect to changing requirements ?
Since this a web parsing system, no sooner this system is build than it is impounded by parsing all the different web pages. Soon it is reported that this system must also handle parsing of RSS feeds.
How will the system adapt to parsing RSS feeds.
Both of the above classes are not closed for modification.
So what went wrong ?
Rule # 2 - Public methods should have an abstract declaration. This can be achieved by abstraction and inheritance. Having an abstract declaration for each public method ensures that a change of requirement can be fulfilled by substituting a new implementation of the abstract method in a different class, without affecting an already existing class.
Design structure of example #1 represents an immature and mixed-up distribution of responsibility between classes. Immature because it does not take care of the fallouts of the changing requirements. Mixed-up because class ParsingHandler is handling parsing of too many varied sources.
Let's re-design example # 1:
Why not each Source class be themselves be responsible for parsing their content. Since each source class precisely know its type and has the knowledge of text that it needs to parse, hence the parsing logic for each doctype may be moved to the respective sources.
Interaction of parsing handler class and the different source classes must be via a layer of abstraction. With abstraction, the parsing handler class will no longer have to worry about the actual source. It can simply use the public abstract method, and thus be totally relieved of the changing requirements, which otherwise could have propagated from the source classes.
Let's see the re-designed classes.
HtmlSource class renamed to HtmlParser as it now handles responsibility of html parsing. Similarly class XmlSource renamed to XmlParser
Software Entities ( Classes, Methods, etc ) should be open for extension but closed for modification
A requirement of change in a class, many a times, cascades into spiraling changes across other classes and other dependent modules. Such a fragile, rigid and non-extinsible program puts a tremendous constraint on the incremental development as well as code maintenance.
Open closed principle, precisely , addresses this problem. It advocates that a program once tested and in production, should no longer be open to any further changes. A program is not open to any change, except on error conditions. Any change in the requirements should be incorporated only and only by extending the class through inheritance.
What does a program achieve on its confirmation to OCP
- A low coupling between the program the clients or other dependent programs. This in turn promotes development that is flexible, extensible and resuable, leading to better development speed as well as low cost.
How to find if the given program violates OCP
Simply, When a change in requirement gives rise to change in a class.
But doesn't this sound outrageous. A change in requirement for a given class behavior must be incorporated in the class itself, isn't it ?
But doesn't this sound outrageous. A change in requirement for a given class behavior must be incorporated in the class itself, isn't it ?
Well that's true, but why not use inheritance to implement the new behavior by extending the base class. The benefit of extension is that the existing working code which is well tested, is not messed with.
But this brings forth few questions :
But this brings forth few questions :
- Are there any pitfalls of extending a class behavior ?
- How to take care that the new extended class do not override the Base class's behavior in a skewed way ?
It would be interesting to answer these questions, which shall be taken separately as Liskov Substitution Principle.
Let's take few program examples that violate OCP
Code Examples
Example # 1
This examples represents a Web Parsing system, capable of parsing different web pages on internet.
Class ParsingHandler - This is responsible for parsing different kinds of Sources, like HTML , XML, etc.
enum DocType - Enumeration of Different kinds of available source.
HtmlSource - Class to encapsulate Html source content and its doc type.
/** * Different types of Source that can be parsed. */ enum DocType { XML, HTML }
/** * Interface for the Source to be parsed. */ interface Source { DocType getType(); String getSource(); }
/** * HTML source class */ class HtmlSource implements Source { private String htmlSource; public HtmlSource(String htmlSource) { this.htmlSource = htmlSource; } public DocType getType() { return DocType.HTML; } public String getSource() { return this.htmlSource; } }
/** * XML source class */ class XmlSource implements Source { public String xmlSource; public XmlSource(String xmlSource) { this.xmlSource = xmlSource; } public DocType getType() { return DocType.XML; } public String getSource() { return this.xmlSource; } }
/** * Parses different sources */ public class ParsingHandler { public DomTree handle(Source source) { DocType type = source.getType(); if (type == DocType.HTML) { return parseHtml(source.getSource()); } else if (DocType.XML == type) { return parseXml(source.getSource()); } return null; } private DomTree parseHtml(String src) { // logic for parsing an html content into a DOM structure } private DomTree parseXml(String src) { // logic for parsing an XML content into a DOM structure } }
So far so good.
But how good this system fares with respect to changing requirements ?
Since this a web parsing system, no sooner this system is build than it is impounded by parsing all the different web pages. Soon it is reported that this system must also handle parsing of RSS feeds.
How will the system adapt to parsing RSS feeds.
- enum DocType - Have to be modified to add a new type like RSS.
- ParsingHandler - Since the handle() method must have the knowledge of the type of source it is parsing, hence this class needs a modification too.
Both of the above classes are not closed for modification.
/** * This class Violates Open Closed Principle. It is not closed for modifications. * Needs to modified each time a new source is added to the system */ public class ParsingHandler { /** * This method behaviour depends upon the type of source. What happends if * the a new source type is added to the system ? This method have to be * modified to include the logic for parsing the new source type. So this * class is not closed and violates OCP */ public DomTree parse(Source source) { DocType type = source.getType(); if (type == DocType.HTML) { return parseHtml(source.getSource()); } else if (DocType.XML == type) { return parseXml(source.getSource()); } else if (DocType.RSS == type) { return parseRss(source.getSource()); } return null; } private DomTree parseHtml(String src) { // logic for parsing an html content into a DOM structure } private DomTree parseXml(String src) { // logic for parsing an XML content into a DOM structure } private DomTree parseRss(String src) { // logic for parsing RSS feeds } }
So what went wrong ?
How to avoid OCP violation
If we look at the Responsibility that the class ParsingHandler handles, it becomes clear that this class is cluttered with varied responsibilities, of parsing sources of different types. Clearly, the parsing logic needs to taken out from this class. But then, where should this logic be placed ?Abstraction , Inheritance and Encapsulation
Encapsulation closely resembles the concept of Information hiding. Here information includes both the data and behavior. When data and behavior is combined ( encapsulated ) into one unit, the question arises is , how should this be exposed to other classes interacting with this unit.
A class should be designed so that it represents a well defined unit of responsibility, exposing only a limited, minimum information about itself to the outside interacting classes. This helps to attain high cohesion and low coupling. A changes in a given class should not produce a ripple effect. Classes must be build in a way so that a change in the class has no or minimal impact on other interacting classes. The more information a class keeps to itself, the lesser are the impact of changes on other dependent classes.
As a class is an encapsulation of data and methods, so both member variables and methods should be secured with a minimum public access.
Rule # 1 - Member variables, must all be declared private. Only the final constants and immutable objects
are immune to be declared as publicRule # 2 - Public methods should have an abstract declaration. This can be achieved by abstraction and inheritance. Having an abstract declaration for each public method ensures that a change of requirement can be fulfilled by substituting a new implementation of the abstract method in a different class, without affecting an already existing class.
Design structure of example #1 represents an immature and mixed-up distribution of responsibility between classes. Immature because it does not take care of the fallouts of the changing requirements. Mixed-up because class ParsingHandler is handling parsing of too many varied sources.
Let's re-design example # 1:
Why not each Source class be themselves be responsible for parsing their content. Since each source class precisely know its type and has the knowledge of text that it needs to parse, hence the parsing logic for each doctype may be moved to the respective sources.
Interaction of parsing handler class and the different source classes must be via a layer of abstraction. With abstraction, the parsing handler class will no longer have to worry about the actual source. It can simply use the public abstract method, and thus be totally relieved of the changing requirements, which otherwise could have propagated from the source classes.
Let's see the re-designed classes.
HtmlSource class renamed to HtmlParser as it now handles responsibility of html parsing. Similarly class XmlSource renamed to XmlParser
/** * Represents layer of abstraction */ abstract class Parser { abstract DomTree parse(String source); }
/** * Handles complete responsibility of parsing xml source */ class XmlParser extends Parser { DomTree parse(String source) { // logic for parsing XML content } }
/** * Handles complete responsibility of pasing html source */ class HtmlParser extends Parser { DomTree parse(String source) { // logic for parsing HTML content } }
/** * Does not violate Open Closed Principle */ class ParsingHandler { public DomTree handle(Parser parser, String src) { return parser.paser(src); } }
New requirement for parsing RSS feeds can, now, be simply accomplished by adding a new class RssParser, without modifying any of the existing classes.
Tuesday, December 10, 2013
Interface Segregation Principle
This Principle states that :
Client must not be forced to use Interfaces that it does not use
Interface Segregation Principle advocates spliting of large interfaces with many methods of varying behaviour into smaller interfaces. These splitted smaller interfaces are also known as 'Role Interfaces' where the large interface is also known as 'Fat Interface' or 'Polluted Interface'.
Client must not be forced to use Interfaces that it does not use
Interface Segregation Principle advocates spliting of large interfaces with many methods of varying behaviour into smaller interfaces. These splitted smaller interfaces are also known as 'Role Interfaces' where the large interface is also known as 'Fat Interface' or 'Polluted Interface'.
What is Achieved
ISP is intented to keep the code easy to read, refactor and manage. This essentially promotes a High Cohesion of the classe responsibilities.
How to find if a given Object Structure Violates ISP
If a class implements an interface but some of the interface methods are not required and hence contain 'NIL' implementation.
This simply means that, If a class implements an interface then all the methods of the inteface MUST and MUST have a behavioural impact on the given class. Each method implemented must certainly define a concrete behaviour for the class. A method must NOT be implemented just for the sake of meeting some 'Structural requirements' of code design. By Structural requirements, I mean, that some methods are just added ( implemented ) to classes because classes need to conform to a particular Type and not because the classes need a particular Behaviour.
So interfaces address two concerns :
- Interface defines a Type for the classes. They are used to achieve polymorphism as well tocontrol the structural design aspects of the Object structure, like loose coupling, etc
- Intefaces also define necessary behaviour of the classes.
Use of point#1 together with the neglect of point#2 gives rise to VIOLATION of Interface Segregation Principle.
How to avoid this Violation
Simpy separate the methods of the Interface into more that one smaller interfaces, taking care that each interface does not contain any unsed methods. Classes can implement few of these smaller interfaces and thus ascertain that they do not have any 'NIL' implementation.
Code Examples
Lets consider a Issue Workflow system. The system allows creation of Issues ( can be compared to Jira Issue ) . Once created, an Issue can be feed into a Workflow system. This workflow system allows the Issues to move from one stage to another. Each stage of workflow consists of allowing an User to add some additional Inputs, like file attachments or comments or code reviews. This workflow system also allows the Issue to revert back to a previous stage.
Below is the workflow interface. addInput() and stopProgress() methods depend upon Attributable interface. The Attributable interface represents abstraction for set of atributes that can be added at any given workflow statge.
interface Workflow { void startProgress(); void stopProgress(Attributable attr); void addInput(Attributable attr); void revert(); boolean isInProgress(); }
There are Two concrete classes that Implement the Workflow interface.
Class NormalWorkflow : It is an Implementation of Workflow that supports all the behaviours defined by Workflow interface. This interface, apart from other operation, can also be reverted and hence implements revert() method.
Class DecisiveWorkflow : This class is very similar to the NormalWorkflow in behaviour, but unlike NormalWorkflow class, this class does NOT support revert behaviour. But since DecisiveWorkflow implements Workflow hence it contains a 'NIL' implementation for revert() method.
class DecisiveWorkflow implements Workflow { // Other part of Code Omitted /** * This method not supported by this class. NIL Implementation */ public void revert() { // Do nothing } }
class NormalWorkflow implements Workflow { // Other part of Code Omitted /** * This method not supported by this class. NIL Implementation */ public void revert() { // Logic for Reverting a Workflow stage } }
Here Workflow Interface is an example of a Polluted Interface. Clearly this Design VIOLATES Interface Segregation Principle as DecisiveWorkflow class is forced to Implement 'revert()' method, though it no usable behaviour for it !!
To make a ISP compliant design, Workflow interface must be split into two interfaces. Since revert() method is not a generalisation of the Differernt kinds of Workflows ( as there can be Workflows that do not need a revert behaviour, like the DecisiveWorkflow class of the above example ) hence revert() method is taken out and made a part of a different Interface.
So after split we get two Role Interfaces, which we can name like RevertableWorkflow and NonRevertableWorkflow
Now the above Design structure is ISP compliant. DecisiveWorkflow class no longer forced to implement revert() method.
Clearly DecisiveWorkflow class is now more readable and maintainable and adheres to High Cohesion principle.
S O L I D
Saturday, December 7, 2013
Liskov Substitution Principle
This principle states:
References to the Base class must be completely substitutable by references of any of the Derived classes.
This principle was formulated by Barbara Liskov. In her own words :
"What is wanted here is something like the following substitution property: If for each object O1 of type S there is an object O2 of type T such that for all programs P deļ¬ned in terms of T, the behavior of P is unchanged when O1 is substituted for O2 then S is a subtype of T"
Once a design structure conforms to Open Closed Principle, the obvious question arises, as to what are the rules of Sub classing ? The open closed principle which states that A class is closed for modifications but open for extension. So are there any general rules to be taken care of while sub classing, violation of those would also mean violation of the open closed principle for the derived sub class ?
References to the Base class must be completely substitutable by references of any of the Derived classes.
This principle was formulated by Barbara Liskov. In her own words :
"What is wanted here is something like the following substitution property: If for each object O1 of type S there is an object O2 of type T such that for all programs P deļ¬ned in terms of T, the behavior of P is unchanged when O1 is substituted for O2 then S is a subtype of T"
Once a design structure conforms to Open Closed Principle, the obvious question arises, as to what are the rules of Sub classing ? The open closed principle which states that A class is closed for modifications but open for extension. So are there any general rules to be taken care of while sub classing, violation of those would also mean violation of the open closed principle for the derived sub class ?
See Open closed Principle
SubTyping
In programming language theory, subtyping (also subtype polymorphism or inclusion polymorphism) is a form of type polymorphismin which a subtype is a datatype that is related to another datatype (the supertype) by some notion of substitutability, meaning that program elements, typically subroutines or functions, written to operate on elements of the supertype can also operate on elements of the subtype. If S is a subtype of T, the subtyping relation is often written S <: T, to mean that any term of type S can be safely used in a context where a term of type T is expected. The precise semantics of subtyping crucially depends on the particulars of what "safely used in a context where" means in a given programming language
See wiki link to subtyping
See wiki link to subtyping
What does a program achieve on its conformation to LSP
Violation of this principle also gives rise to violation of the Open closed principle.If there is a method that do not conform to Liskov's substitution principle ( LSP ) then that method contain references to the supertype but depend on the knowledge of the subtypes. This means that when a new subtype is added to the system, then this method must be modified to incorporate the knowledge of this newly created subtype. Modification of the Base class when a new subtype is added to the system is a clear violation of Open Closed Principle.This necessitates formulation or rules that must be taken care of while subtyping a given supertype.
Any attempt to subclass a given Base class must take care that the Derived classes do not beak any 'behaviour' of the Base class. Such an unintentational discrepancy of behaviour may occur when the Derived class does some value additions to the Base class.
Behaviour of Base class is defined by its Invariants, Preconditions and Postconditions. This behaviour must not break when a Derived class extends the Base class.
Any attempt to subclass a given Base class must take care that the Derived classes do not beak any 'behaviour' of the Base class. Such an unintentational discrepancy of behaviour may occur when the Derived class does some value additions to the Base class.
Behaviour of Base class is defined by its Invariants, Preconditions and Postconditions. This behaviour must not break when a Derived class extends the Base class.
How to find if a given Object Structure Violates LSP
For class heirarchies to conform to LSP, below conditions must be satisfied
- The Invariants of the Super Types MUST NOT be violated or broken by the SubType. ( Illustrated by Example # 1 )
- PreConditions on an overridden method for the SubType must be a 'SUBSET' of the precondition on the same method for the SuperType. In other words, Preconditions must not be strengthened by the SubType. ( Illustrated by Example # 2 )
- PostConditions on an overridden method for the SubType must be a 'SUPERSET' of the preconditions on the same method for the SuperType. In other words, PreConditions must not be weakened by the SubType.( Illustrated by Example # 3 )
Code Examples
Example # 1
This examples consist of a system of Rectangles and Squares. This example illustrates violation of LSP as the Invariants of the Super class are violated by the Subclass
Rectangle - This class has two member variables, width and height. Area is calculated as width * height.
Invariant imposed by Rectangle class
- Both the width and the height can vary independently
Square - This class extends Rectangle. Since a Square has shares a lot of geometrical similarities with the Rectangle, it is quite tempting to assume that a Square ISA type of rectangle.
Invariant imposed by Square class
- As for a square, width = height , always, so this class allows both width and height to change simultaneously. This violates LSP as it breaks the invariant condition of the Rectangle class
/** * Here width and Height can vary independently */ public class Rectangle { protected int width; protected int height; public int getWidth() { return width; } public void setWidth(int width) { this.width = width; } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } public long getArea() { return getWidth() * getHeight(); } }
/** Here widh and height vary simultaneously. * Attempt to change the width also changes the height, and vice versa. */ class Square extends Rectangle { public void setWidth( int width ) { super.setWidth(width); super.setHeight(width); } public void setHeight( int height ) { super.setHeight(height); super.setWidth(height); } }
class LspTest { private static Rectangle getRectangle( String s ) { if ( "rectangle".equals( s )) return new Rectangle(); if ( "square".equals(s )) return new Square(); return null; } public static void main (String args[]) { Rectangle r = getRectangle( "rectangle" ); r.setWidth(5); r.setHeight(10); long areaRectangle = r.getArea(); System.out.println( areaRectangle ); // prints 50 assert( areaRectangle == (long) 50 ); // PASSES ! // Below case will fail // Indicates Violation of LSP Rectangle s = getRectangle( "square"); s.setWidth(5); s.setHeight(10); long areaSquare = r.getArea(); System.out.println( areaSquare ); // prints 100 !! assert( areaSquare == (long) 50 ); // FAILS ! } }
Example # 2
This examples represents a system of Pencil and Pens. Here Both Pencil and Pen are Concrete implementations of the abstract class Writer.
Both Pencil and Pen classes share a common abstraction of writing. Pencil extends this abstract class Writer and overrides its behaviour for writing.
Pen class also shares this abstract behaviour of writing, so Pen class Extends Pencil and overrides its write() method.
So far so OK.
Lets see the preconditions on the write() method for each of the Pencil and Pen classes.
Pencil - There are NO Preconditions on write() method for this class. Pencil class always writes to the console for every call to the write() method.
Also there are no Postconditions on method write() for Pencil class
Pen - This class has certain Preconditions on its write() method.
Preccondition on write() method for the Pen class
Behaviour of Pen is such that a Pen shall write only when it's Ink Level is above a threshold. The Pen class is instantiated with a certain amount of Ink Fill. For the sake of programmatic simplicity, I have assigned the Ink threshold to half the value of the Initial Ink Fill. Everytime the Pen writes ( via call to write() method ), the amount of Ink fill decreases by Unit One.
If the amount of Ink fill is less than the Threshold value, the Pen refuses to write() and throws a RuntimeException( " Cannot write. Do a Refill " );
This Precondition strengthens the Precondition of the sueprclass Pencil and thus Violates LSP
This Precondition strengthens the Precondition of the sueprclass Pencil and thus Violates LSP
/** * Abstraction for write behaviour */ public abstract class Writer { public abstract write(); }
/** * Pencil class always writes to the console */ public class Pencil extends Writer { public void write( String toWrite ) { System.out.println( toWrite ); } }
/** * Pen class extends Pencil and overrides the write() method with PreConditions */ class Pen extends Pencil { private int THRESHOLD_AMOUNT; private int fillAmount; public Pen( int fillAmount ) { super(); this.fillAmount = fillAmount; THRESHOLD_AMOUNT = fillAmount / 2; } /* * VIOLATES Liskov's Substitution Principle ! */ public void write( String toWrite ) { if ( !isFilled() ) throw new RuntimeException("Pen not Filled. Please Refill"); write0(toWrite); decreaseFillAmount(); } public void reFill() { fillAmount = THRESHOLD_AMOUNT * 2; } private boolean isFilled() { return fillAmount > THRESHOLD_AMOUNT; } private void write0( String toWrite ) { System.out.println(toWrite); } private void decreaseFillAmount() { --fillAmount; } }
/** * This class tests the LSP behaviour. */ public class TestLSP { public static void main( String[] args ) { Pencil pencil = new Pencil(); Pencil pen = new Pen( 4 ); /* * Pencil writes to the console for all the 4 iterations of Loop */ for ( int i = 0 ; i< 4; i++ ) { pencil.write("do write"); } /* * Pen class writes only 2 times, after which its Ink amount falls below threshold and it throws a Exception * This is a VIOLATION of LSP, because if the Pen class is substituted for Pencil then the program will * behave erratically with Exceptions whenever the precondition fails */ for ( int i = 0 ; i< 4; i++ ) { pen.write("do write"); // throws exception after 2 iterations } } }
Example # 3
This example represents a system to track and monitor the attendance records of the office comers.( Attendees )
Attendee - This class represents an Attendee. Any human being qualifies to be an Attendee
Attendance - Abstract class to define the abstract behaviour of Attendance when an Attendee visits the Office. This is abstracted by the method comesToOffice( Attendee officeComer )
OfficeAttendance - This class extends Attendance class and overrides comesToOffice() behaviour.
This class imposes certain Postconditions on this behaviour.
Postconditions on comesToOffice() Method for OfficeAttendance class :
- All Office attendees must be registered in the Attendance register. So every office comer must necessarily be marked as 'attended'.
OfficeWatcher - This class extends OfficeAttendance class and overrides comesToOffice() behaviour. This class has an additional behaviour 'securityCheck()' for every attendee coming to office, which is perfectly fine as this is what an Office Watcher should do ! But, Apart from this additional behaviour of security check, this class also weakens the PostConditions of the OfficeAttendance superclass.
PostConditions on comesToOffice() Method for OfficeWatcher class :
PostConditions on comesToOffice() Method for OfficeWatcher class :
- Not all Office attendees are registered in the Attendance register. Only the Attendees who are Employees of the given office are marked as 'attended'. This PostConditions , thus, weakens the PostCondition on the comeToOffice() method of the superclass ( OfficeAttendace ). This class Violates LSP
public abstract class Attendance { public abstract void comesToOffice( Attendee officeComer ); }
public class Attendee { /* * Encapsulates behaviour for an Office Attendee. Any human being can be a Office Attendee */ }
public class OfficeAttendance extends Attendance { private Attendance register; public void comesToFiice( Attendee officeComer ) { sayHelloToOfficeComer( officeComer ); markAttendance( officeComer ); } protected void markAttendance( Attendee officeComer ) { /* * store the office coomer's name along with the time of entry ( current system time ) */ save( officeComer, System.currentTimeMillis() ); } protected void sayHelloToOfficeComer( Attendee officeComer ) { officeComer.greet( " Hello. Welcome to my Office" ); } }
/** * This class VIOLATES LSP as comesToOffice() method of this class * weakens the PostConditions of Base class ( OfficeAttendance ) */ public class OfficeWatcher extends OfficeAttendance { /** * This method violates LSP as it does not satisfy the Post Conditions of the Base class */ public void comesToOffice( Attendee officeComer ) { sayHelloToOfficeComer(officeComer); /* * Base class's Post Condition that every office attendee must definitely be marked in the attendance register * is not fulfilled here because of the conditional check. * This violates LSP. */ if ( officeComer.isThisOfficeEmployee( officeComer ) ) { markAttendance(officeComer); } doSecurityChecks( officeComer ); } private doSecurityChecks( Attendee officeComer ) { /* * does some security checks */ } }
How to avoid Violation of LSP
Any attempt to subclass a given base class must take care that the derived class do not beak any behaviour of the Base class. Such an unintentational discrepancy of behaviour may occur when the Derived class does some value additions to the Base class.
How can we ensure that such a discrepancy does not occcur, which effectively means to find the objective ways exhibiting the occurrence of such a discepancy.
As already discussed, below conditions must always be satisfied to conform to LSP
As already discussed, below conditions must always be satisfied to conform to LSP
Conditions to LSP conformation
- The Invariants of the Super Types MUST NOT be violated or broken by the SubType. ( Illustrated by Example # 1)
- PreConditions on an overridden method for the SubType must be a 'SUBSET' of the precondition on the same method for the SuperType. In other words, Preconditions must not be strengthened by the SubType. ( Illustrated by Example # 2 )
- PostConditions on an overridden method for the SubType must be a 'SUPERSET' of the postconditions on the same method for the SuperType. In other words, PostConditions must not be weakened by the SubType. ( Illustrated by Exampe # 3 )
Design by Contract
Design by contract is a software design approach that prescribes that a software design should follow a formal, precise and verifiable interface specifications for software components with preconditions, postconditions and invariants.
See wiki link to Design by Contract
See wiki link to Design by Contract
S O L I D
Subscribe to:
Posts (Atom)