Object Design - Part 1

3,640 views

Published on

Published in: Technology

Object Design - Part 1

  1. 1. OBJECT DESIGN Dhaval Dalal software-artisan.com @softwareartisan Part I: OO Design Guidelines
  2. 2. DESIGN SMELLS Tell-tale signs of poor architecture Source: Robert C. Martin
  3. 3. DESIGN SMELLS Needless Repetition (Duplication) Same code appears over and over again in different forms. Rigidity (Interdependence) Every change causes too many changes in other parts of the system. In order to make system robust, it has to be insensitive to small changes, i.e, a small change to a problem should lead to only a small change in the solution.
  4. 4. DESIGN SMELLS Immobility (Cannot Disentangle) It is hard to reuse in another application because it cannot be disentangled from the current application. Fragility (Easily Breakable) A change affects unexpected parts of the system and causes it to break.
  5. 5. DESIGN SMELLS Opacity (Difficulty in Understanding) Tendency of a module to be difficult to understand. Needless Complexity (Over Engineering) Results from speculative approach (gold-plating!). UseYAGNI
  6. 6. DESIGN SMELLS Viscosity (Toughness in preserving design) High DesignViscosity Design preserving methods are harder to employ than hacks. Easy to do the wrong thing, but hard to do the right thing. EnvironmentViscosity
  7. 7. OO DESIGN GUIDELINES Strong Commitment to Encapsulation Strong Commitment to Loose Coupling Law of Demeter Single Responsibility Principle Coding to Super-Types & LSP EncapsulateVariations & apply OCP Create Role-Based Behaviors Dependency Inversion Favor Composition over Inheritance Dependency Injection Dependency Elimination
  8. 8. STRONG COMMITMENT TO ENCAPSULATION Ask (or query) an Object to get something for you. if (tier.getType() == SILVER) { // calculate loyalty points } // Still Query, but encapsulated if (tier.isSilver()) { // calculate loyalty points } Tell (or command) an Object to do something for you. tier.loyaltyPoints(amountSpent)
  9. 9. TELL, DON’T ASK! Procedural code gets information, inspects it and then makes decisions. Its all about how to do. Object-oriented code tells objects to do things. Declare or State (think declaratively) what you want and do not process (not procedurally) what you want.
  10. 10. PRESERVE ENCAPSULATION Getters and Setters generally break encapsulation. Don’t introduce getters or setters for all fields of an object...just because IDEs provide this facility. Don’t introduce getters just because tests need it. engine.start(); assertThat(engine.isRunning(), is(true)); Use Observable Behavior instead, this improves the API assertThat(engine.start(), is(true));
  11. 11. PRESERVE ENCAPSULATION Avoid returning a private collection public class Building { private List<Floor> floors = new ArrayList<Floor>(); //... public List<Integer> getFloors() { return floors; } } Instead return a copy or a read-only version of it. public class Building { private List<Floor> floors = new ArrayList<Floor>(); //... public List<Integer> getFloors() { return Collections.unmodifiableList(floors); } }
  12. 12. STRONG COMMITMENT TO LOOSE COUPLING A E D C B dog.getBody().getTail().wag() dog.expressHappiness() dog.getBody().getTail().setLength(50) state altering “train wrecks” “train wrecks” refactored dog.getBody().getTail().getLength() state querying “train wrecks”
  13. 13. YET ANOTHER FORM A E D C B // In one place, we do Body body = dog.getBody(); // and in some other place // in codebase we do Tail tail = body.getTail(); tail.setLength(50);
  14. 14. LAW OF DEMETER No “Train Wrecks” Send messages only to object’s immediate neighbors. Principle of least knowledge Don’t talk to more objects than required. The more objects you talk to, more the risk of getting broken when any one of the object changes. Restrict class interaction in order to minimize coupling among classes.
  15. 15. CONFORMING TO LAW OF DEMETER A method of an Object should invoke only the methods of the following kinds of objects itself its parameters any objects it creates or directly collaborates any composite object As a guidance-rule, more than two “dots” means the Law of Demeter is being violated.
  16. 16. SINGLE RESPONSIBILITY PRINCIPLE (SRP) There should never be more than one reason to change a class. A responsibility is an axis of change. Key is in identifying which is an axis of change and which is not? Many responsibilities means many reasons to change. This is hard to see, as we think responsibility in groups.
  17. 17. WHY SHOULD RESPONSIBILITIES BE SEPARATED INTO SEPARATE CLASSES? Because each responsibility is an axis of change. When the requirements change, that change will be manifest through a change in responsibility amongst the classes. If a class assumes more than one responsibility, then there will be more than one reason for it to change. Source: Robert C. Martin
  18. 18. THE CHALLENGE IN SRP IS… The SRP is one of the simplest of the principle, and one of the hardest to get right. Conjoining responsibilities is something that we do naturally. Finding and separating those responsibilities from one another is much of what software design is really about. Source: Robert C. Martin
  19. 19. BENEFITS OF SRP… Removes the “Immobility Smell” from Design Immobility is the inability to reuse software from other projects or from parts of the same project. Deodorizes the “Rigidity Smell” Generates a highly cohesive design.
  20. 20. CODING TO WHAT? Coding to an interface class Rectangle implements Shape { … } Shape s = new Rectangle(); s.draw(); Coding to an implementation Rectangle r = new Rectangle(); r.draw(); Code to Interfaces, not to Implementation
  21. 21. SUBTYPING & INHERITANCE Subtyping a.k.a Interface Inheritance Behavioral Subtyping (Semantic Relationship - “is-a”) Inheritance a.k.a Implementation Inheritance Code Reuse (Syntactic Relationship - “re-use”) Subclass re-uses code from base-class and may over-ride operations, by replacing base-class implementation with its own. Inheritance is not Subtyping (William Cook)
  22. 22. SUBTYPING EXAMPLE class Rectangle implements Shape { private float width, height; Rectangle(float width, float height) { this.width = width; this.height = height; } public float area() { return width * height; } public float perimeter() { return 2 * (height + width); } } class RTriangle implements Shape { private float base, height; RTriangle(float base, float height) { this.base = base; this.height = height; } public float area() { return 0.5 * base * height; } public float perimeter() { //… return perimeter } }
  23. 23. INHERITANCE EXAMPLE - 1 class Square extends Rectangle { Square(float side) { super(side, side); } } class Rectangle { private float width, height; Rectangle(float width, float height) { this.width = width; this.height = height; } public void setWidth(float newWidth) { width = newWidth; } public void setHeight(float newHeight) { height = newHeight; } public float area() { return width * height; } } class Reshaper { void reshape(Rectangle r, float width, float height) { r.setWidth(width); r.setHeight(height); } }
  24. 24. Reshaper shaper = new Reshaper(); Rectangle r = new Rectangle(2, 3); shaper.reshape(r, 5, 3); r.area(); Square s = new Square(2); shaper.reshape(s, 5, 3); s.area(); Implementation inheritance does not provide assurance of polymorphic substitutability. Will this give correct result? INHERITANCE EXAMPLE - 1
  25. 25. class Point2D { private int x, y; Point2D(int x, int y) { this.x = x; this.y = y; } //Do 2D rotation public Point2D translate(int by) { … } //Do 2D scaling public Point2D scale(int factor) { … } //rotation about origin public Point2D rotate(int by) { … } public int getX() { return x; } public int getY() { return y; } } class Point3D extends Point2D { private int z; Point3D(int x, int y, int z) { super(x, y); this.z = z; } @Override // Do 3D translation public Point3D translate(int by) { … } @Override // Do 3D scaling public Point3D scale(int factor) { … } // rotation about 3 axes public Point3D rotateX(int by) { … } public Point3D rotateY(int by) { … } public Point3D rotateZ(int by) { … } public int getZ() { return z; } } INHERITANCE EXAMPLE - 2
  26. 26. Point3D overrides translate and scale in Point2D to make it appropriate for 3D and introduces additional rotation methods, leaving the rotate in Point2D intact. When we find ourselves modifying almost all methods in the sub-class then we are not really using inheritance, we are trying to get generality offered by subtype polymorphism. Be vigilant of such a situation and use interface to clarify sub-typing from inheritance. Source: A Little Book on Java INHERITANCE EXAMPLE - 2
  27. 27. INHERITANCE Shape + area() : float + perimeter() : float RectangleRTriangle Pentagon Rectangle Square SUBTYPING INHERITANCE is about Gaining attributes and functionality of super-type SUB TYPING is about Compatibility of Interfaces
  28. 28. INHERITANCE & SUBTYPING In Java, C#, C++, Scala, Inheritance and Subtyping are welded together, i.e. Subtyping occurs automatically when you have inheritance. Inheritance == Subtyping However, Inheritance and Subtyping are different. Inheritance != SubTyping Java supports subtype polymorphism (using interfaces) independently of inheritance (inherit from single class). As there is no code in interface, there is nothing to inherit. Source: A Little Book on Java
  29. 29. LISKOV’S SUBSTITUTION PRINCIPLE (LSP) Sub-types must be substitutable for their Base-types Be able to operate upon derivatives of that base class without knowing it. It makes clear that in OO Design “IS-A” relationship is about behavioral semantics. Not intrinsic private behavior, but extrinsic public behavior. E.g. You cannot pass a Stack where Queue is required. Semantics that clients can depend upon.
  30. 30. LSPVIOLATION Shape + area() : float + perimeter() : float RectangleRTriangle Pentagon Source: Robert C. Martin
  31. 31. LSPVIOLATION Shape + area() : float + perimeter() : float RectangleRTriangle Pentagon Circle + area () + circumference () This is a CLEAR violation of LSP! Source: Robert C. Martin
  32. 32. LSPVIOLATION Shape + area() : float + perimeter() : float RectangleRTriangle Pentagon Circle + area () + circumference () This is a CLEAR violation of LSP! Square This is a SUBTLE violation of LSP! Source: Robert C. Martin
  33. 33. HANDLING CHANGES When a single change to a program Results in a cascade of changes to dependent modules. The program becomes rigid, unpredictable and not reusable. This is sign of a bad design
  34. 34. ENCAPSULATE VARIATIONS Identify things that vary and separate them from the concepts that stay the same. Evolve an Abstraction representing the concept. Introduce Interface or Abstract super-type. Unify variations on this pivot of abstraction. Encapsulate each variation using a Concrete Class that implements/extends the above super-type.
  35. 35. APPLY OPEN/CLOSED PRINCIPLE It says that you should design modules that never change. When requirements change, extend the behavior of modules By adding new code. Not by changing old code that already works.
  36. 36. OPEN/CLOSED PRINCIPLE (OCP) Software Entities (Classes, Modules, Methods, etc…) Should be Open for Extension but Closed for Modification.
  37. 37. OPEN FOR EXTENSION Behavior of the module can be extended. Make the module behave in new and different ways as the requirements of the application change. The source code of such a module is inviolate. Resist making source code changes to it. CLOSED FOR MODIFICATION
  38. 38. HOW CAN THESE TWO OPPOSING
 ATTRIBUTES BE RESOLVED? Abstraction is the Key.
  39. 39. WHY ABSTRACTIONS? Its about naming the concept. We like to retain flexibility to never make up the mind about anything until you are forced to do it. Abstraction is one way of doing this. They are a way to control complexity. They help us contain the guts of the stuff while avoiding spill-over to things we are building. They are the truths that do not vary when the details change. Source: Structure and Interpretation of Computer Programs
  40. 40. WHAT MAKES A GOOD ABSTRACTION? An Abstraction focuses on Client’s perspective and suppresses implementation perspective. A simple logical model that can be described in a sentence or a two. It does not mean class’s processing is simple! It simply means that the class is focused on a small set of activities. Source: S/W Design Studio, by Dr. Jim Fawcett, Syracuse University
  41. 41. NATURE OF ABSTRACTIONS They represent “hinge points” for the places where the design can bend or be extended. This flexibility comes with a cost of themselves being rigid to change. Resist the temptation to introduce an abstraction unless absolutely essential, introducing a pre-mature abstraction leads to pre-mature design optimisation.
  42. 42. CREATE ROLES-BASED BEHAVIORS Name a role around a metaphor or around the concepts which you are abstracting out. This helps a client understand and relate to an object’s behavior. Role should convey everything about the implemented behavior.
  43. 43. OCP IN REALITY 100% closure is not attainable. Closure must be strategic. We design systems such that they are closed to the most likely kinds of changes that we foresee. Source: Robert C. Martin
  44. 44. DEPENDENCY STRUCTURE: PROCEDURAL DESIGN Source: Robert C. Martin Dependencies are transitive
  45. 45. DEPENDENCY INVERSION PRINCIPLE (DIP) High level modules should not depend on low level modules. Both should depend upon abstractions. Abstractions should not depend upon specifics. Specifics should depend upon abstractions. Source: Robert C. Martin
  46. 46. DEPENDENCY STRUCTURE: OO DESIGN Source: Robert C. Martin Dependencies are inverted
  47. 47. POLICY AND MECHANISM Source: Robert C. Martin High level modules contain policy decisions and business models. When High level modules (Policies) depend upon low level modules, changes to low level modules (Mechanisms) most likely will force upon a change in them. Ideally, it should be the other way around, high level modules should be forcing lower level ones to change.
  48. 48. SEPARATE POLICY FROM MECHANISM…WHY? Source: The Art of Unix Programming Hard-wiring policy and mechanism Makes policy rigid and hard to change. Change in policy has a strong tendency to destabilise mechanisms Policy changes frequently. Separating the two Makes it possible to experiment with new policy without breaking mechanisms. Easier to write tests for mechanisms.
  49. 49. SEPARATNG POLICY FROM MECHANISM Source: Robert C. Martin
  50. 50. EXTENDING OBJECT CAPABILITIES Composition promotes “dynamic reuse” Is a weaker association Vary object behavior dynamically by changing collaborators Inheritance promotes “static reuse” Is a stronger association Vary object behavior at compile time. “has-a” can be better than “is-a”, leads to supple design
  51. 51. SUB-CLASS PROLIFERATION Evils of Inheritance Deep hierarchies obscure understanding. As a guidance, question yourself when you find creating hierarchies that are more than 2 levels deep. Contractual changes on super-class ripple through Wide hierarchies. In general, Favor Composition over Inheritance
  52. 52. INTERFACE SEGREGATION PRINCIPLE (ISP) Deals with avoiding “Fat”, Non-cohesive class interfaces. Groups of methods used by different clients. Don’t force clients to depend on methods they don’t use. Else they are subject to changes to those methods. Results in an inadvertent coupling between all the clients.
  53. 53. ABSTRACTION PROLIFERATION Make these code blocks Too many abstractions make it hard to understand the system.
  54. 54. LOOK MA....NO COUPLING! Too many abstractions make it hard to understand the system. Refactor to Closures/Lambdas wherever its sensible and possible Dependency Elimination You don’t need abstract classes or interfaces to achieve polymorphism. Close your class against modifications.
  55. 55. YET ANOTHER GUIDELINE Dependency Elimination can be better than Dependency Inversion. Requires Language Level support to achieve this Scala, Groovy, Ruby, C# and now Java8 make this possible. Not possible with Java7. Many patterns like Strategy, Command etc... get subsumed where Closures are available
  56. 56. DEGREES OF COUPLING Creating Concrete Collaborator from within a client Injecting Concrete Collaborator into a client (Dependency Injection) Injecting Code Block into a client (Dependency Elimination) Direct Loose No Coupling
  57. 57. OBJECT DESIGN GUIDELINES SUMMARY Design for preserving Encapsulation Tell, Don’t Ask! Extend Object Capabilities By favoring Composition over Inheritance Use Mixins, if available and possible Use Inheritance when nothing of the above works. Design for High Cohesion SRP, ISP
  58. 58. OBJECT DESIGN GUIDELINES SUMMARY Design for Reduced Coupling Law of Demeter, OCP, LSP. Separate Policy from Mechanism (DIP) Design for No Coupling Dependency Elimination
  59. 59. OBJECT DESIGN GUIDELINES SUMMARY Uphold Good Housekeeping Policy. Use and clean your own stuff… Uphold Green Environment Policy. Undo changes made by you to your environment.
  60. 60. REFERENCES Agile Principles, Patterns and Practices Robert C. Martin and Micah Martin Object Design Rebecca Wirfs-Brock and Alan McKean Alec Sharp The Pragmatic Programmers Mock Roles, not Objects Paper by Steve Freeman, Nat Pryce,Tim Mackinnon, Joe Walnes InfoQ presentation by Steve Freeman and Nat Pryce Head-First Design Patterns Book Venkat Subramaniam’s Blog SICP - Abelson & Sussman

×