Design Principles iwongu at gmail dot com
What is Object-Oriented design?
Dependency Management
First Version All designs start well void copy() { int ch; while ( (ch = ReadKeyboard()) != EOF) WritePrinter(ch); }
Second Version Oh, no! Nobody said the requirements might change! bool gTapeReader = false; // remember to reset this flag void copy() { int ch; while ( (ch = gTapeReader ? ReadTape() : ReadKeyboard()) != EOF) WritePrinter(ch); }
Third Version How unexpected! Requirements changed again! bool gTapeReader = false; bool gTapePunch = false; // remember to reset these flags void copy() { int ch; while ( (ch = gTapeReader ? ReadTape() : ReadKeyboard()) != EOF) gTapePunch ? WritePunch(ch) : WritePrinter(ch); }
Wonderful Use Change Rot Smell Redesign
Design Smells The odors of rotting software It’s rigid. It’s fragile. It’s not reusable.
Rigidity Rigidity is the inability to be changed The impact of a change cannot be predicted. If not predicted, it can not be estimated. Time and cost can not be qualified. Managers become reluctant to authorize change.
Fragility Software changes seem to exhibit non-local effects. A single change requires a cascade of subsequent changes. New errors appear in areas that seem unconnected to the changed areas Quality is unpredictable. The development team loses credibility.
Immobility It's not reusable. Desirable parts of the design are dependent on undesirable parts. The work and risk of extracting the desirable part may exceed the cost of redeveloping from scratch.
Example of a good design First and only version! void copy(FILE* in, FILE* out) { int ch; while ( (ch = fgetc(in)) != EOF) fputc (ch, out); } But, wait! Aren't we supposed to be learning OO design? This isn't OO, is it?
… Is it? It's a small program based on abstraction! FILE is an abstraction. It represented some kind of byte stream. It has many variations. It has methods. The methods are dynamically bound. FILE is a class, just implemented differently.
Rephrased in OO First and only version! interface Reader { char read(); } interface Writer { void write(char c); } public class Copy { Copy(Reader r, Writer w) itsReader = r; itsWriter = w; } public void copy() int c; while ( (c = itsReader.read()) != EOF ) itsWriter.write(c); Reader itsReader; Writer itsWriter;
CHANGE
“CHANGE” The one constant in software development
“Belady and Lehman’s Laws” Software will continually change “Belady and Lehman’s Laws” Software will continually change. Software will become increasingly unstructured as it is changed.
Ities of Software Quality Reliability Efficiency Readability Understandability Modifiability, Maintainability Testability Portability
UML Unified Modeling Language
Class
Association
Generalization
Dependency
UML Class Diagram shows the relationships of Classes. Dependency Association Aggregation Composition Generalization Realization
Design Principles
Design Principles SRP Single Responsibility Principle OCP Open Closed Principle LSP Liskov Substitution Principle DIP Dependency Inversion Principle ISP Interface Segregation Principle
SRP Single Responsibility Principle
There should never be more than one reason for a class to change.
In the context of the SRP, a responsibility means "a reason for change
Each responsibility is an axis of change.
Orthogonal class Y class X No need to change
Non-Orthogonal class Y class X Need to change
SRP Violation
SRP
But, it’s not easy to see SRP.
But, it’s not easy to see SRP.
Question?
OCP Open-Closed Principle
Software entities should be open for extension but closed for modification.
But how?
Abstraction is the key.
enum ShapeType { circle, square }; struct Shape { ShapeType itsType; }; struct Circle { ShapeType itsType; }; struct Square { ShapeType itsType; }; void DrawAllShapes(Shape* list[], int n) { for (int i = 0; i < n; ++i) { Shape* s = list[i]; switch (s->itsType) { case square: DrawSquare((Square*)s); break; case circle: DrawCircle((Circle*)s); break; } } }
struct Shape { virtual void Draw() const = 0; }; struct Square : Shape { virtual void Draw() const; }; struct Circle : Shape { virtual void Draw() const; }; void DrawAllShapes(Shape* list[], int n) { for (int i = 0; i < n; ++i) { Shape* s = list[i]; s->Draw(); } }
OCP Violation If new shapes are needed,
OCP Violation
OCP If new shapes are needed,
OCP
But, OCP is not just inheritance.
OCP is the root motivation behind many of the heuristics and conventions. For example,
Make All Member Variables Private.
No Global Variables – Ever.
RTTI is Dangerous.
Question?
LSP Liskov Substitution Principle
Subtypes should be substitutable for their base types.
Rectangle
Square IS-A rectangle.
Square
void Square::set_width(double w) {. Rectangle::set_width(w); void Square::set_width(double w) { Rectangle::set_width(w); Rectangle::set_height(w); } void Square::set_height(double h) { Rectangle::set_width(h); Rectangle::set_height(h); }
double g(Rectangle& r) { r.set_width(5); r.set_height(4); assert(r.area() == 20); }
Square IS-A rectangle. But, Square is NOT substitutable for rectangle.
Violating the LSP often results in the use of Run-Time Type Information (RTTI).
double g(Rectangle& r) {. r. set_width(5);. r. set_height(4); double g(Rectangle& r) { r.set_width(5); r.set_height(4); if (dynamic_cast<Square*>(&r) != 0) { assert(r.area() == 16); } else { assert(r.area() == 20); } }
It’s also a violation of OCP.
Using DBC, LSP means,
DBC? Design By Contract. Precondition Postcondition Invariant
A routine redeclaration may only replace the original precondition by one equal or weaker, and the original postcondition by one equal or stronger.
Base Derived
The postcondition of Square::set_width() is weaker than the postcondition of Rectangle::set_width().
Rectangle Square
Question?
DIP Dependency Inversion Principle
a. High-level modules should not depend on low-level modules a. High-level modules should not depend on low-level modules. Both should depend on abstractios.
b. Abstractions should not depend on details b. Abstractions should not depend on details. Details should depend on abstractions.
Structured Design
Dependency Inversion
Dependency Inversion
Depend on abstractions.
No variable should hold a pointer or reference to a concrete class No variable should hold a pointer or reference to a concrete class. No class should derive from a concrete class. No method should override an implemented method of any of its base classes.
But, it’s impossible. For example, someone has to create the instances of the concrete class, and whatever module does that will depend on them.
And, it might not be a problem to depend on concrete but non-volitile classes.
DIP Violation
DIP
Question?
ISP Interface Segregation Principle
Door And, we need a timed door.
Timer
The first solution What are problems?
Not all varieties of Door need timing. Violation of LSP.
The interface of Door has been polluted with a method that it does not require. Fat Interface.
ISP Interface Segregation Principle Clients should not be forced to depend on methods that they do not use.
Multiple inheritance
Delegation
Question?
References
http://objectmentor. com/ http://objectmentor http://objectmentor.com/ http://objectmentor.com/resources/omi_reports_index.html http://objectmentor.com/resources/publishedArticles.html → Robert C. Martin