ObjectOriented Design Principles M S 1 st Wonjae
Object-Oriented Design Principles M. S. 1 st Wonjae Choi (wonjaechoi 2014@gmail. com) Hanyang Univ. Software Technology and Architecture Research Lab.
Contents Ø Symptoms of Rotting Design Ø SRP(The Single Responsibility Principle) Ø OCP(The Open-Closed Principle) Ø LSP(The Liskov’s Substitution Principle) 2/26
Symptoms of Rotting Design Primary symptoms that tell us that our designs are rotting Ø Rigidity Ø Fragility Ø Immobility Ø Viscosity Ø Unnecessarily Complexity Ø Needless Repetition 3/26
SRP(The Single Responsibility Principle) A class should have one, and only one reason to change • A class should be well-named, should have minimal and complete operations • Operations in the class should be closely related to one subject area Þ Strong cohesion, Low coupling • Why was it important to separate responsibilities into separate classes? Þ Because each responsibility is an “axis of change” or “a reason for change” • If a class has more than one responsibility, then the responsibilities become coupled Þ This kind of coupling leads to fragile designs 4/26
Violation of SRP Example • Two different applications use the Rectangle class Þ CGA : calculation, GA : draw • This design violates SRP, The Rectangle class has two responsibilities • The violation of SRP causes several nasty problems 1. We must include the GUI in the CGA => GUI have to be linked in, consuming link time, compile time, memory foot print 2. If a change to the CGA causes the Rectangle to change for some reason, that change may force us to rebuild, retest, and redploy the CGA => If we forget this, that application may break unpredictable ways 5/26
SRP applied Example • A better design is to separate the two responsibilities into two completely different classes • Now changes made to the way rectangles are rendered cannot affect the CGA 6/26
An axis of change is an axis of change only if the change actually occurs (1/2) Modem interface violates SRP • Connection • Data. Channel SRP applied design 7/26
An axis of change is an axis of change only if the change actually occurs (2/2) But • If application does not change in ways that cause the two responsibilities to change at different times, separating them would smell of needless complexity The SRP is one of the most simplest of the principles, and one of the hardest to get right 8/26
OCP (The Open-Closed Principle) Software entities should be open for extension, but closed for modification The OCP is the single most important guide for the OO designers 1. They are “Open For Extension” Þ This means that the behavior of the module can be extended 2. They are “Closed For Modification” Þ This means that the source code of a module should not be changed How can these two opposing attributes be resolved? Þ Abstraction is the Key 9/26
Abstraction Create abstractions that are fixed and yet represent an unbounded group of possible behaviors The abstraction is abstract base class The unbounded group of possible behaviors is all the possible derivative classes A module that manipulates only an abstraction can be closed for modification Þ It depends on an abstraction this is fixed The behavior of the module can be extended by creating new derivatives of the abstraction 10/26
Two Common ways to satisfy the OCP • <<interface>> Policy Client is not open and closed +policy. Function() -service. Function() Mechanism -service. Function() • Base class is open closed (Template Method Pattern) Client is both open closed The key is a clear separation of generic functionality from the detailed implementation of that functionality 11/26
Shape Abstraction Example • We have an application that must be able to draw circles and squares on a standard GUI • The circles and squares must be drawn in a particular order • A list of the circles and squares will be created in the appropriate order and the program must walk the list in that order and draw each circle or square 12/26
Procedural Solution Violating OCP enum Shape. Type {circle, square}; struct Shape{Shape. Type its. Type; }; struct Circle{Shape. Type its. Type; double its. Radius; Point its. Center; }; struct Square{Shape. Type its. Type; double its. Side; Point its. Top. Left; }; void Draw. Square(struct Square*) void Draw. Circle(struct Circle*); typedef struct Shape *Shape. Pointer; void Draw. All. Shapes(Shape. Pointer list[], int n){ int i; for (i=0; i<n; i++){ struct Shape* s = list[i]; switch (s->its. Type){ case square: Draw. Square((struct Square*)s); break; case circle: Draw. Circle((struct Circle*)s); break; } } } 13/26
OO Solution Conforming to OCP Shape +draw(): void Rectangle Circle +draw(): void class Shape {public: virtual void Draw() const = 0; }; class Square : public Shape { public: virtual void Draw() const; }; class Circle : public Shape { public: virtual void Draw() const; }; void Draw. All. Shapes(Set<Shape*>& list) { for (Iterator<Shape*>i(list); i; i++){ (*i)->Draw(); } 14/26
Strategic Closure • No significant program can be 100% closed! • The designer must choose the kind of changes against which to close his design • The designer must guess the most likely kinds of changes, and then construct abstractions to protect him those changes Eg. , precious solution is not closed against change that all Circles should be drawn before any Squares But, Resisting premature abstraction is as important as abstraction itself! - Apply abstraction only to those parts of program that exhibit frequent changes - Premature abstraction leads to needless complexity 15/26
The Primary mechanisms behind OCP • The Primary mechanisms behind OCP are abstraction and polymorphism • One of the primary mechanisms that supports abstraction and polymorphism is inheritance • What are design rules that govern the use of inheritance to support the OCP? • What are the characteristics of the best inheritance hierarchy? • What are the traps that will causes us to create hierarchies that do not conform to the OCP? 16/26
LSP(The Liskov’s Substitution Principle) Derived types must be completely usable through tge base class interface without the need for the user to know the difference • Subtypes must be substitutable for their base types (super types) • The importance of this principle becomes obvious when you consider the consequences of violating it 17/26
A Simple Example of a Violation of LSP void Draw. Shape(const Shape& s) { if (typeid(s) == typeid(Square)) Draw. Square(static_cast<Square&>(s)); else if (typeid(s) == typeid(Circle)) Draw. Circle(static_cast<Circle&>(s)); } • • Clearly the Draw. Shape function is badly formed It must know about every possible derivative of the Shape class, and it must be changed whenever new derivatives of Shape are created Þ Violation of the OCP Violation of the LSP is a latent violation OCP 18/26
More Subtle Violation • • A square is a rectangle for all normal intents and purposes. Since ISA relationship holds, it is logical to model the Square class as being derived from Rectangle Square • Square’s width and height is identical • Override set. Width, set. Height 19/26
The Real Problem • • The model is now self consistent, and correct However, this conclusion would be amiss, A model that is self consistent is not necessarily consistent with all users void g(Rectangle& r){ r. Set. Width(5); r. Set. Height(4); assert(r. Get. Width() * r. Get. Height()) == 20); } • • • The programmer who wrote that function justified in assuming that changing the width of a Rectangle leaves its height Therefore, there exist functions that take pointers or references to Rectangle objects, but cannot operate properly upon Square objects This function exposes a violation of the LSP The addition of the Square derivative of Rectangle has broken these function So the OCP has been violated 20/26
Validity is not Intrinsic • A model, viewed in isolation cannot be meaningfully validated • The validity of a model can only be expressed in terms of its clients For example, when we examined the final version of the Square and Rectangle classes in isolation, they were self consistent and valid But, look at them from a view point of a client who made a reasonable assumptions about the base class, the model broke down Þ Thus, when considering whether a particular design is appropriate or not, one must not simply view the solution in isolation Þ one must view it in terms of the reasonable assumptions that will be made by the users of that design 21/26
What Went Wrong • • • Why did the apparently reasonable model of the Square and Rectangle go bad? After all, isn’t a Square a Rectangle? Doesn’t ISA relationship hold? No! Þ Because the behavior of a Square object is not consistent with that of a Rectangle object Þ Behaviorally, a Square is not a Rectangle! Þ And it is the behavior that software is all about In order to the LSP to hold, all derivatives must conform to the extrinsic behavior that clients expect of the base class The LSP makes clear that in OOD, the ISA relationship pertains to extrinsic public behavior that clients depend on 22/26
Design by Contract There is a strong relationship between the LSP and the concept of Design by Contract as expounded by Bertrand Meyer Using this scheme, methods of classes declare preconditions and postconditions Pre-condition must be true in order for the method to execute Þ pre-condition denotes requirements Þ Client check precondition Upon completion, the method guarantees that the post-condition will be true Þ post-condition denotes promises Example) postcondition of Rectangle. set. Width(double w) is assert((width==w) && (height==old. height)) 23/26
The rule for derivatives As stated by Meyer, • …when redefining a routine, you may only replace its pre-condition by a weaker one, and its post-condition by a stronger one A Derived class should require no more and promise no less int Base: : f(int x); // REQUIRE : x is odd // PROMISE : return int Derived: : f(int x); // REQUIRE : x is int // PROMISE : return positive int 24/26
Conclusion • The OCP is at the heart of many of the claims made for OOD • It is when this principle is in effect that applications are more maintainable, reusable and robust • The LSP is an important feature of all programs that conform to the OCP • It is only when derived types are completely substitutable for their base types 25/26
Reference • Agile Software Development Principles, Patterns, and Practices, by Robert Cecil Martin(Prentice Hall, 2003) 26/26
- Slides: 26