610 likes | 685 Views
OO Design Principles. Project Group eXtreme Programming. Md. Abul Bashar 07/09/2004. Outline. Problems with software system Basic OO Principles OCP, LSP, DIP Class Cohesion and Coupling Principles ISP, SRP, LoD Package Cohesion Principles REP, CRP, CCP Package Coupling Principles
E N D
OO Design Principles Project Group eXtreme Programming Md. Abul Bashar 07/09/2004
Outline • Problems with software system • Basic OO Principles • OCP, LSP, DIP • Class Cohesion and Coupling Principles • ISP, SRP, LoD • Package Cohesion Principles • REP, CRP, CCP • Package Coupling Principles • ADP, SDP, SAP
What goes wrong with S/W? Changing requirements • You start with a clear picture in your mind what the SW should do. • Then something goes wrong, the software starts to rot: • Changes and Additions are harder to make. • Simplest changes terrify you because of rippling unexpected effects • You must redesign the whole !!
Bad Signs of Rotting Design • Rigidity • Code difficult to change • Management reluctance to change anything becomes policy • Fragility • Even small changes can cause cascading effects • Code breaks in unexpected places • Immobility • Code is so tangled that it’s impossible to reuse anything • a module could be reused in another system, but the effort and risk of separating the module from original environment is too high • Viscosity • Much easier to hack than to preserve original design.
Open-Closed Principle (OCP) • "Software Systems change during their life time" • both better designs and poor designs have to face the changes; • good designs are stable Software entities should be open for extension, but closed for modification B. Meyer, 1988 / quoted by R. Martin, 1996 • Be open for extension • module's behavior can be extended • Be closed for modification • source code for the module must not be changes • Modules should bewritten so they can be extended withoutrequiring them to be modified
Open-Closed Principle (OCP) • Really the Heart of OO Design • 100% satisfaction to OCP is never possible • But we should attempt to minimize the number of modules that do not satisfy it • Conformance to this principle yields the greatest level of reusability and maintainability
Open-Closed Principle (OCP) • Consider the following method of some class: • public double totalPrice(Part[] parts) { • double total = 0.0; • for (int i=0; i<parts.length; i++) { • total += parts[i].getPrice(); • } • return total; • } • The job of the above function is to total the price of each part in the • specified array of parts • If Part is a base class or an interface and polymorphism is being used, • then this class can easily accommodate new types of parts without • having to be modified! • - It conforms to the OCP ??
Open-Closed Principle (OCP) But what if the Accounting Department decided some price increase on motherboard parts and memory parts ? - How about the following code? public double totalPrice(Part[] parts) { double total = 0.0; for (int i=0; i<parts.length; i++) { if (parts[i] instanceof Motherboard) total += (1.45 * parts[i].getPrice()); else if (parts[i] instanceof Memory) total += (1.27 * parts[i].getPrice()); else total += parts[i].getPrice(); } return total; }
Open-Closed Principle (OCP) • Does this conform to the OCP? • Every time the Accounting Department comes out with a new pricing • policy, we have to modify the totalPrice() method! • It is not Closed For Modification. • Obviously, policy changes such as that mean that we have to modify • code somewhere, so what could we do? • To use our first version of totalPrice(), we could incorporate pricing policy • in the getPrice() method of a Part
Open-Closed Principle (OCP) Part Here are example Part and ConcretePart classes: // Class Part is the superclass for all parts. public class Part { private double price; public Part(double price) (this.price = price;} public void setPrice(double price) {this.price = price;} public double getPrice() {return price;} } // Class ConcretePart implements a part for sale. // Pricing policy explicit here! public class ConcretePart extends Part { public double getPrice() { // return (1.45 * price); //Premium return (0.90 * price); //Labor Day Sale } } ConcretePart1 ConcretePart2
Open-Closed Principle (OCP) • But now we must modify each subclass of Part whenever the pricing policy changes! • A better idea is to have a PricePolicy class which can be used to provide different pricing policies: • // The Part class now has a contained PricePolicy object. • public class Part { • private double price; • private PricePolicy pricePolicy; • public void setPricePolicy(PricePolicy pricePolicy) { • this.pricePolicy = pricePolicy; • } • public void setPrice(double price) {this.price = price;} • public double getPrice() {return pricePolicy.getPrice(price);} • }
<< Policy >> Part ConcretePolicy ConcretePart1 ConcretePart2 Open-Closed Principle (OCP) /** * Class PricePolicy implements a given price policy. */ public class PricePolicy { private double factor; public PricePolicy (double factor) { this.factor = factor; } public double getPrice(double price) {return price * factor;} }
Liskov Substitution Principle (LSP) Functions That Use References To Base Classes Must Be Able To Use Objects Of Derived Classes Without Knowing it.
LSP Example // A very nice Rectangle class. public class Rectangle { private double width; private double height; public Rectangle(double w, double h) { width = w; height = h; } public double getWidth() {return width;} public double getHeight() {return height;} public void setWidth(double w) {width = w;} public void setHeight(double h) {height = h;} public double area() {return (width * height); }
LSP Example • Now, What about a Square class? • Clearly, a square is a rectangle, so the Square class should be derived from the Rectangle class, right? Let's see! • Observations: • A square does not need both a width and a height as attributes but it will inherit them from Rectangle anyway. • The inherited setWidth() and setHeight() methods are not really appropriate for a Square, since the width and height of a square are identical. • So we'll need to override setWidth() and setHeight(). • Having to override these simple methods is a clue that this might not be an appropriate use of inheritance!
LSP Example Here's the Square class: // A Square class. public class Square extends Rectangle { public Square(double s) {super(s, s);} public void setWidth(double w) { super.setWidth(w); super.setHeight(w); } public void setHeight(double h) { super.setHeight(h); super.setWidth(h); } } Rectangle setWidth(w) setHeight(h) Circle setWidth(w) setHeight(h)
LSP Example Everything looks good. But check this out! public class TestRectangle { // Define a method that takes a Rectangle reference. public static void testLSP(Rectangle r) { r.setWidth(4.0); r.setHeight(5.0); System.out.println( "Width is 4.0 and Height is 5.0, so Area is " + r.area() ); if (r.area() == 20.0) System.out.println("Looking good!\n"); else System.out.println("Huh?? What kind of rectangle is this??\n"); }
LSP Example public static void main(String args[]) { //Create a Rectangle and a Square Rectangle r = new Rectangle(1.0, 1.0); Square s = new Square(1.0); // Now call the method above. According to the // LSP, it should work for either Rectangles or // Squares. Does it?? testLSP(r); testLSP(s); } }
LSP Example • Test program output: • Width is 4.0 and Height is 5.0, so Area is 20.0 Looking good! • Width is 4.0 and Height is 5.0, so Area is 25.0 Huh?? What kind of rectangle is this?? • Looks like we violated the LSP!
What’s the problem here? • Implicit assumptions • The programmer made the assumption that changing the width of a Rectangle leaves its height unchanged. • Didn’t consider that, the base class can write a method that causes the design model to break down. • Behaviorally, a Square is not a Rectangle! A Square object is not polymorphic with a Rectangle object. • Solutions can not be viewed in isolation.
Liskov Substitution Principle (LSP) • IS-A relationship is all about behavior • All subclasses must conform to the behavior as of base class • Subclass must NOT have more constraints than it’s base class • The guarantee of the LSP is – a subclass can always be used whenever it’s base class is used.
Dependency Inversion Principle (DIP) Depend upon Abstraction, Do NOT Depend upon Concretions.
Dependency Inversion Principle (DIP) I. High-level modules should notdepend on low-level modules. Both should depend on abstractions. II. Abstractions should not depend on details. Details should depend on abstractions R. Martin, 1996 • OCP states the goal; DIP states the mechanism • A base class in an inheritance hierarchy should not know any of its subclasses • Modules with detailed implementations are not depended upon, but depend themselves upon abstractions
DIP Related Heuristic Design to an interface, not an implementation! • Use inheritance to avoid direct bindings to classes: Interface(abstract class) Client Implementation(concrete class)
Design to an Interface • Abstract classes/interfaces: • tend to change much less frequently • abstractions are ‘hinge points’ where it iseasier to extend/modify • shouldn’t have to modify classes/interfacesthat represent the abstraction (OCP) • Exceptions • Some classes are very unlikely to change; • therefore little benefit to insertingabstraction layer • Example: String class • In cases like this can use concrete classdirectly • as in Java or C++
DIP Related Heuristic Avoid Transitive Dependencies • Avoid structures in which higher-level layers depend on lower-level abstractions: • In example below, Policy layer is ultimately dependant on Utility layer. Policy Layer Mechanism Layer UtilityLayer Depends on Depends on
Solution to Transitive Dependencies • Use inheritance and abstract ancestor classes to effectively eliminate transitive dependencies: Policy Layer Mechanism Interface depends on Mechanism Layer UtilityInterface depends on UtilityLayer
DIP Related Heuristic When in doubt, add a level of indirection • If you cannot find a satisfactory solution for the class you are designing, try delegating responsibility to one or more classes: Problem Holder ProblemSolver
When in doubt … • It is generally easier to remove or by-pass existing levels of indirection than it is to add them later: Blue class’s indirect message calls to red class fail to meet some criteria (e.g. real-time constraints, etc.) X So, Blue class re-implements some or all of green class’s responsibilities for efficiency and calls red object directly
Interface Segregation Principle (ISP) Clients should not be forced to depend upon interfaces that they do not use. R. Martin, 1996 • Many client-specific interfaces are betterthan one general purpose interface • Consequence: • impact of changes to oneinterface aren’t as bigif interface issmaller • interface pollution
Single Responsibility Principle (SRP) There Should NEVER be More Than One Reason for a Class to Change.
Single Responsibility Principle (SRP) • More than one reason means More than one Responsibilities. • Responsibilities become coupled
Principles of OO High Level Design • Cohesion Principles • Reuse/Release Equivalency Principle (REP) • Common Reuse Principle (CRP) • Common Closure Principle (CCP) • Coupling Principles • Acyclic Dependencies Principle (ADP) • Stable Dependencies Principle (SDP) • Stable Abstractions Principle (SAP)
Reuse/Release Equivalency Principle The granule of reuse is the granule of release. Only components that are released through a tracking system can be efficiently reused. R. Martin, 1996 • What means this ? • A reusable software element cannot really be reused in practice unless it is managed by a release system of some kind • e.g. release numbers or names • All related classes must be released together • Release granule Reuse granule • no reuse without release • must integrate the entire module (can’t reuse less) • Classes are to small • we need larger scale entities, i.e. package
Common Reuse Principle (CRP) All classes in a package [library] should be reused together. If you reuse one of the classes in the package, you reuse them all. R. Martin, Granularity 1996 • Packages of reusable components should be grouped by expected usage, Not: • common functionality, nor • another arbitrary categorization. • Classes are usually reused in groups based on collaborations between library classes
Common Reuse Principle (CRP) • The Façade Pattern becomes critical when the level of reuse becomes a targeted goal: • If you started with… (e.g. a mess!)
Common Reuse Principle (CRP) The Façade Solution “Façade” says you should clean it up like this:
Common Closure Principle (CCP) The classes in a package should be closed against the same kinds of changes. A change that affects a package affects all the classes in that package R. Martin, 1996 • What means this ? • Classes that change together belong together • Goal: limit the dispersion of changes among released packages • changes must affect the smallest number of released packages • Classes within a package must be cohesive • Given a particular kind of change, either all classes or no class in a component needs to be modified
Reuse vs. Maintenance • REP and CRP makes life easier for re-users • packages very small • CCP makes life easier for maintainer • large packages • Packages are not fixed in stone • early lifetime dominated by CCP • later you want to reuse: focus on REP CRP
Acyclic Dependencies Principles (ADP) The dependency structure for released component must be a Directed Acyclic Graph (DAG) There can be no cycles. R. Martin, 1996
Acyclic Dependencies Principles (ADP) A Cycle Creeps In !
Acyclic Dependencies Principles (ADP) Breaking a cycle
Stable Dependencies Principles (SDP) The dependencies between components in a design should be in the direction of stability. A component should only depend upon components that are more stable than it is. R. Martin, 1996 Depend only upon components whose I metric is lower than yours R. Martin, 1996 SDP Violation
Stable Dependencies Principles (SDP) Where to Put High-Level Design? • High-level architecture and design decisions don't change often • shouldn't be volatile place them in stable packages • design becomes hard to change inflexible design • How can a totally stable package (I = 0) be flexible enough to withstand change? • improve it without modifying it... • Answer: The Open-Closed Principle • classes that can be extended without modifying them Abstract Classes
Stable Abstractions Principles (SAP) The abstraction of a package should be proportional to its stability! Packages that are maximally stable should be maximally abstract. Instable packages should be concrete. R. Martin, 1996 • Ideal Architecture • Instable (changeable) packages on the top • must be concrete • Stable (hard to change) package on the bottom • hard to change, but easy to extend • highly abstract(easily extended) • Interfaces have more intrinsic stability than executable code • SAP is a restatement of DIP
Stable Abstractions Principles (SAP) The Main Sequence Instability (I) should increase as abstraction (A) decreases. R. Martin, 2000