Upgrade to Pro — share decks privately, control downloads, hide ads and more …

OO Inheritance - Not Always Evil - Refactoring ...

OO Inheritance - Not Always Evil - Refactoring to Open-Closed with Inheritance

Refactoring an unmaintainable mess of nested conditionals to the Open/Closed principle - using OO inheritance -- an example - Inheritance is not always evil!

Based on Sandi Metz's 'All the Little Things' http://www.confreaks.com/videos/3358-railsconf-all-the-little-things

Philip Schwarz

March 20, 2015
Tweet

More Decks by Philip Schwarz

Other Decks in Programming

Transcript

  1. Refactoring an unmaintainable mess of nested conditionals to the Open/Closed

    principle - using OO inheritance - - an example - Inheritance is not always evil! © 2014 Philip Schwarz https://twitter.com/philip_schwarz [email protected] This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.
  2. This talk complements and extends some aspects of my OCP

    talk: Downloadable at https://github.com/philipschwarz/presentations
  3. Shortly after giving the OCP talk I watched the following:

    Sandi’s talk complements and extends parts of mine so well http://www.poodr.com/ that I just HAVE TO show you the video!
  4. So this is not your regular talk. I’ll wrap my

    slides around her talk • slides at the beginning: • to give you additional background that helps you follow the video • to link back to the topics of my other presentation • slides at the end: to recap and elaborate some of Sandi’s key points What I’ll actually be doing is showing you the video of Sandi’s talk (A talk within a talk!).
  5. The Open-Closed Principle Modules should be both open and closed

    Bertrand Meyer Object Oriented Software Construction 1988 We want modules to be both for extension and for modification
  6. If non-OO methods are all we have, then Meyer says

    we face a change or copy dilemma CHANGE COPY
  7. A B C D E A module and its clients

    A’ F G H I New clients which need A’, an adapted or extended version of A Typical situation where the needs for Open and Closed modules are hard to reconcile = client of
  8. Analysability Stability Reliability solution - - - Analysability Stability Reliability

    solution - - - With non-OO methods, there are only only 2 solutions available to us, BOTH UNSATISFACTORY multiple maintenance problem Change by Modification CHANGE COPY
  9. A B C D E A’ F G H I

    So how can we have modules that are both and ? How can we keep A and everything in the top part of the figure unchanged, … …while providing A’ to the bottom clients, and avoiding duplication of software?
  10. A B C D E A’ F G H I

    With the OO concept of inheritance Inheritance allows us to get out of the CHANGE OR COPY dilemma… …because inheritance allows us to define a new module A' in terms of an existing module A, …by stating only the differences between the two A’ defines new features, and redefines (i.e. modifies) one or more of A’s features inherits from Change by Addition
  11. Hacking = Slipshod approach to building and modifying code Slipshod

    = Done poorly or too quickly; careless. The Hacker may seem bad but often his heart is pure.
  12. He sees a useful piece of software, which is almost

    able to address the needs of the moment, more general than the software’s original purpose. Hacker Spurred by a laudable desire not to redo what can be reused, our hacker starts modifying the original to add provisions for new cases solution
  13. The impulse is good but the effect is often to

    pollute the software with many clauses of the form if that_special_case then… if (<special case D>) then … if (<special case C>) then … if (<special case B>) then … if (<special case A>) then … switch
  14. Open-Closed Principle = One way to describe the OCP and

    the consequent OO techniques is to think of them as organised hacking Hacking The organised form of hacking will enable us to cater to the variants without affecting the consistency of the original version. Inheritance Change by Modification Change by Addition
  15. extends is evil!!!!! But, using inheritance is no longer the

    main approach to satisfying the OCP Allen Holub 2004 2003
  16. Using inheritance is still one of the ways of satisfying

    the OCP, and was considered THE approach for a long while Why extends is evil 1988 - 1st ed. 1997 – 2nd ed. 1995 That started changing with the emergence of the design techniques presented in Design Patterns 2003
  17. multiple maintenance problem Change by Modification CHANGE solution COPY solution

    Hacker Change by Addition OCP solution Chooses Chooses switch extends is evil!!!!! But…
  18. Sandi deals with a perfect example of the unmaintainable mess

    that you end up with when you keep extending code by adding conditionals switch
  19. REFACTOR Sandi deals with the 43-line conditional by refactoring it

    so it satisfies the Open Closed Principle switch
  20. Inheritance Sandi uses the original approach to satisfying the OCP:

    using OO inheritance So we get a nice example of the technique inheritance is not evil, and I can tell you exactly when it is safe to use it
  21. switch Sandi’s talk also… looks a little bit at why

    switch creep takes root acts as an example of refactoring from procedural code to OO code contains other bits of her design wisdom Refactoring
  22. A code kata is an exercise in programming which helps

    a programmer hone their skills through practice and repetition A kata is an exercise in karate where you repeat a form many, many times, making little improvements in each The intent behind a code kata is similar
  23. The Gilded Rose Inn An Inn is where travellers can

    seek lodging and, usually, food and drink The Gilded Rose Kata centers around a fictional Inn in a game called World of Warcraft
  24. Hi and welcome to team Gilded Rose. As you know,

    we are a small inn with a prime location in a prominent city ran by a friendly innkeeper named Allison. http://iamnotmyself.com/2011/02/13/refactor-this-the-gilded-rose-kata/ The Original Gilded Rose Kata Aged Brie Sulfuras, Hand of Ragnaros Backstage Pass to a TAFKAL80ETC concert +5 Dexterity Vest Elixir of the Mongoose We also buy and sell only the finest goods. Unfortunately, our goods are constantly degrading in quality as they approach their sell by date. We have a system in place that updates our inventory for us. It was developed by a no-nonsense type named Leeroy, who has moved on to new adventures.
  25. Your task is to add the new feature to our

    system so that we can begin selling a new category of items. First an introduction to our system…
  26. ‘sellIn’ denotes the number of days we have to sell

    the Item int sellIn = 4; All items have a sellIn value
  27. At the end of each day the system lowers both

    values for every item for (Item item : items) { } sellIn -= X quality -= Y
  28. quality -= Y Once the sell by date has passed…

    …quality degrades twice as fast x 2
  29. • Once the sell by date has passed, Quality degrades

    twice as fast • The Quality of an item is never negative • “Aged Brie” actually increases in Quality the older it gets • The Quality of an item is never more than 50 • “Sulfuras”, being a legendary item, never has to be sold or decreases in Quality • “Backstage passes”, like aged brie, increases in Quality as it’s SellIn value approaches; Quality increases by 2 when there are 10 days or less and by 3 when there are 5 days or less but Quality drops to 0 after the concert That’s just the first of several additional ‘rules’ affecting items:
  30. It is not hard to imagine developers implementing those requirements

    one at a time, each time adding one or more branches to the conditional. switch
  31. We have recently signed a supplier of conjured items. Feel

    free to make any changes to the UpdateQuality method and add any new code as long as everything still works correctly. However, do not alter the Item class or Items property as those belong to the goblin in the corner who will insta-rage and one-shot you as he doesn’t believe in shared code ownership Conjured Item Conjured Mana Cake X This requires an update to our system: “Conjured” items degrade in Quality twice as fast as normal items
  32. Emily Bache This is designed as a refactoring kata, where

    you take this less than clean code, and transform it via small steps into something that can be maintained and extended. Conjured Item When you have the code under control, it should be easy to add the new feature for “Conjured” items.
  33. Two approaches to this Kata • In the original version

    of this Kata, there were no tests provided, only a textual description of the requirements. • In a later addition, I added Text-Based (aka Approval) tests. Emily Bache So you can do this kata in two ways, • either writing your own tests, and practice writing really good ones • or just jump straight to the refactoring part, leaning on the text-based tests.
  34. Sandi takes neither of these approaches: https://github.com/jimweirich/gilded_rose_kata The original had

    no tests. Since this is a refactoring kata, I feel the tests are important and provide a fairly complete test suite. Just delete the tests if you wish to "go it alone". she uses a Ruby version of the kata that already has tests
  35. Sandi also ignores the restriction we saw earlier: ‘do not

    alter the Item class or Items property’ In fact she goes further: she doesn’t even look at the explanation of the problem. She just takes the code and tries to add the required new functionality X “But I didn’t do that [look at the problem explanation]. I wanted to treat this problem as if it was a real production problem, and that my only source of information was the tests and the code”
  36. Watch ‘All the Little Things’ (just under 40 minutes long)

    https://www.youtube.com/watch?v=8bZh5LMaSmE http://www.confreaks.com/videos/3358-railsconf-all-the-little-things
  37. RailsConf 2014 Ruby on Ales 2014 There are two versions

    of Sandi’s Talk: In recapping, I’ll sample from both versions.
  38. Your task is to add the new feature to our

    system so that we can begin selling a new category of items.
  39. I went and I tried. I tried really hard, but

    I failed miserably. I could not do it. I spent hours trying… I found it impossible
  40. if … then … else … IMPOSSIBLE ALL If it

    is so hard, if it is impossible to change that if statement, and I am supposed to be all OOP, then you have to wonder why I even tried. What made me choose as my strategy, changing that if statement?
  41. Well it is because I felt I was supposed to

    do it. And here is what happens, right? you write some code someone asks for a change What do we do? You go looking around the codebase for code that is the closest thing to the thing that you are trying to do. You put the new code there
  42. if … then … else … if … then …

    else … Maybe the first person that wrote it put an if statement in. And if statements exert gravitational pull. and if that thing already has an if statement, well they just put in another branch on it, right? that’s how it works. Novices especially, they are afraid to make new objects so the just go put more code in where they can find the thing they are trying to add
  43. So, the natural tendency of code is to grow bigger

    and bigger and bigger, and there comes a point when it gets big enough that it tips DIS and it feels like even if you are the kind of person who would normally make a new class, that you would be doing the past and the future a disservice by putting the code anywhere else.
  44. at that point it [the if statement] is so big

    that you cannot imagine putting code anywhere else. if … then … else … if … then … else … when you have a 5000 line in an active record, you do not make a 20 line service object that goes with it when you have a new requirement, you feel like you have to put the code where it is Service X
  45. Let’s take a 2 minute detour to see a real-world

    example of a conditional that has grown well beyond the tipping point.
  46. If the pattern is a good one then the code

    gets better, and if the pattern is a bad one, we exacerbate the problem. We have a bargain to follow the pattern.
  47. And so the pattern failed me. That if statement, it

    felt like it had evolved, evolutionarily, if … then … else … if … then … else … and that any change I made I was supposed to make there
  48. But fortunately I couldn’t do it. And so I decided

    I would make a new pattern: I am not going to try to add Conjured, I am going to refactor the code so that it is simpler and so that I can then add Conjured. Conjured Item
  49. Despite the fact that people tell you ‘never ever ever

    use inheritance’… inheritance is not evil!!!!! X
  50. 1. The hierarchy is shallow and narrow, it is not

    deep and wide If those conditions are all true for the thing that you are doing I will give you dispensation to use it in a very specific case 2. The subclasses use every bit of code that is in the superclass 3. If you draw a mental image of your object graph, this little hierarchical cluster is at the edge of the graph (it is on a leaf node of your object hierarchy) then it is hard to find another more intention-revealing way to write this code.
  51. 1. The hierarchy is shallow and narrow, it is not

    deep and wide 2. The subclasses use every bit of code that is in the superclass Let’s now look at how Sandi elaborates on her first two conditions In her book: POODR
  52. 1. The hierarchy is shallow and narrow, it is not

    deep and wide A hierarchy’s shape is defined by its overall breadth and depth It is this shape that determines ease of use, maintenance, and extension. Here are a few of the possible variations of shape:
  53. Easy to understand Slightly more complicated. A bit more challenging

    Have a tendency to get wider Difficult to understand and costly to maintain SHOULD BE AVOIDED
  54. The problems with deep hierarchies They have a very long

    search path for message resolution They provide numerous opportunities for objects in that path to add behavior as the message passes by. Objects depend on everything above them, so a deep hierarchy has a large set of built-in dependencies, each of which might someday change. Programmers tend to be familiar with just the classes at their tops and bottoms; * They tend to understand only the behavior implemented at the boundaries of the search path * The classes in the middle get short shrift. * Changes to these vaguely understood middle classes stand a greater chance of introducing errors.
  55. 2. “The subclasses use every bit of code that is

    in the superclass” What does she mean? Let’s look at how she elaborates this in POODR Subclasses are specializations of their superclasses. A MountainBike should be everything a Bicycle is, plus more. Any object that expects a Bicycle should be able to interact with a MountainBike in blissful ignorance of its actual class. These are the rules of inheritance; break them at your peril.
  56. For inheritance to work…the objects that you are modeling must

    truly have a generalization–specialization relationship. [When] subclasses…are not truly specializations of their superclasses, the hierarchy becomes untrustworthy. IS-A __ trust
  57. Untrustworthy hierarchies force objects that interact with them to know

    their quirks trust Inexperienced programmers do not understand and cannot fix a faulty hierarchy
  58. Knowledge of the structure of the hierarchy leaks into the

    rest of the application, creating dependencies that raise the cost of change. if (bicycle instanceof MountainBike) { // do XYZ } if (bicycle instanceof MountainBike) { // do XYZ } if (bicycle instanceof MountainBike) { // code that knows about } often by explicitly checking the classes of objects. when asked to use one they will embed knowledge of its quirks into their own code,
  59. All of the code in an abstract superclass should apply

    to every class that inherits it Superclasses should not contain code that applies to some, but not all, subclasses. ======= ======= ======= ======= ======= ======= ======= ======= ======= ======= ======= ======= ======= ======= ======= =======
  60. When interacting with these awkward objects, programmers are forced to

    know their quirks and into dependencies that are better avoided. Faulty abstractions cause inheriting objects to contain incorrect behavior; attempts to work around this erroneous behavior will cause your code to decay.
  61. Subclasses that fail to honor their contract are difficult to

    use. They’re “special”and cannot be freely substituted for their superclasses. These subclasses are declaring that they are not really a kind-of their superclass and cast doubt on the correctness of the entire hierarchy. Subclasses are not permitted to do anything that forces others to check their type in order to know how to treat them or what to expect of them. if (bicycle instanceof MountainBike) { // code that knows } trust IS-A __
  62. Let’s look in more detail at the LSP to better

    understand what Sandi means by contract, and substitutability When you honor the contract, you are following the Liskov Substitution Principle, which is named for its creator, Barbara Liskov, and supplies the “L” in the SOLID design principles Barbara Liskov The Liskov Substitution Principle (LSP) - 1988
  63. Let q(x) be a property provable about objects x of

    type T. Then q(y) should be provable for objects y of type S where S is a subtype of T. [in a Type hierarchy] the supertype’s behavior must be supported by the subtypes: subtype objects can be substituted for supertype objects without affecting the behavior of the using code. Behaviour of ----> supported by ----> 2000
  64. [the LSP] allows using code to be written in terms

    of the supertype specification, yet work correctly when using objects of the subtype. For example, code can be written in terms of the Reader type, yet work correctly when using a BufferedReader. private void foo(BufferedReader bufferedReader) throws IOException { … bar(bufferedReader); … } private void bar(Reader reader) throws IOException { … System.out.println( reader.read() ); … }
  65. the substitution principle requires that the subtype specification support reasoning

    based on the supertype specification. Three properties must be supported: 1. Signature Rule 2. Methods Rule 3. Properties Rule
  66. The subtype objects must have all the methods of the

    supertype, and the signatures of the subtype methods must be compatible with the signatures of the corresponding supertype methods. This rule is enforced by the compiler, so we are all familiar with it. #1 The Signature Rule
  67. 1. Signature Rule 2. Methods Rule 3. Properties Rule Before

    we can cover the other two rules we must briefly look at Design by Contract The other two rules cannot be checked by a compiler because they require reasoning about the meaning of specifications
  68. Viewing the relationship between a class and its clients as

    a formal agreement , expressing each party’s rights and obligations DbC uses preconditions, postconditions and class invariants to document (or better, programmatically assert) the contract between classes, methods and their callers. 1986 Bertrand Meyer Design by Contract (DbC) DbC
  69. Conditions that must always be true of a class. They

    are implicitly added to the preconditions and postconditions of every method. DbC Object Oriented Software Construction Conditions that must be true before a method can execute. if the conditions are not met, it is a bug in the client. Conditions that must be true when a method is finished executing. if the conditions are not met, it is a bug in the method.
  70. #2 The Methods Rule Calls of these subtype methods must

    “behave like” calls to the corresponding supertype methods. The subtype objects must have all the methods of the supertype, and the signatures of the subtype methods must be compatible with the signatures of the corresponding supertype methods. #1 The Signature Rule What does Liskov mean by “behave like”? Let’s look at Meillir-Jones’ explanation, in terms of preconditions and postconditions
  71. • Every operation’s precondition is no stronger than the corresponding

    operation in the superclass • Every operation’s postcondition is at least as strong as the corresponding operation in the superclass a subtype method can • expect the same or less • promise the same or more Explanation of “methods must ‘behave like’ calls to the corresponding supertype methods” Meillir-Jones A subtype method can weaken the precondition and can strengthen the postcondition 1999
  72. #2 The Methods Rule Calls of these subtype methods must

    “behave like” calls to the corresponding supertype methods. The subtype objects must have all the methods of the supertype, and the signatures of the subtype methods must be compatible with the signatures of the corresponding supertype methods. #1 The Signature Rule The subtype must preserve all properties that can be proved about supertype objects. #3 The Properties Rule The class invariant of a subclass must be equal to or stronger than that of its superclass
  73. An Example of a subclass violating the contract of its

    superclass Robert Martin (aka Uncle Bob) Agile Software Development Principles, Patterns and Practices 2002
  74. public class Rectangle { private Point topLeft; private double width;

    private double height; public void setWidth(double width) { this.width = width; } public double getWidth() { return width; } public void setHeight(double height) { this.height = height; } public double getHeight() { return height; } … } Imagine that this application works well and is installed in many sites. One day, the users demand the ability to manipulate squares in addition to rectangles. It is often said that inheritance is the IS-A relationship. In other words, if a new kind of object can be said to fulfill the IS-A relationship with an old kind of object, the class of the new object should be derived from the class of the old object.
  75. public class Rectangle { … public void setWidth(double width) {

    this.width = width; } … public void setHeight(double height) { this.height = height; } … } For all normal intents and purposes, a square is a rectangle. So it is logical to view the Square class as being derived from the Rectangle class. IS-A public class Square extends Rectangle { }
  76. public class Rectangle { … @Ensures({“getWidth() = width”}) public void

    setWidth(double width) { this.width = width; } … @Ensures({“getHeight() = height”}) public void setHeight(double height) { this.height = height; } … } @Invariant(“getWidth() = getHeight()”) public class Square extends Rectangle { } A square has the invariant that its width and height are identical, but Square inherits Rectangle’s setters, which do not preserve the invariant. public void f(Square square) { square.setWidth(5); assert square.getHeight() == 5; } There is a problem though: The assertion fails To see why, let’s make the contract explicit
  77. public class Rectangle { … @Ensures({“getWidth() = width”}) public void

    setWidth(double width) { this.width = width; } … @Ensures({“getHeight() = height”}) public void setHeight(double height) { this.height = height; } … } We can sidestep the problem by overriding the setters. @Invariant(“getWidth() = getHeight()”) public class Square extends Rectangle { @Ensures({“getWidth() = width”}) public void setWidth(double width) { this.width = width; this.height = width; } @Ensures({“getHeight() = height”}) public void setHeight(double height) { this.height = height; this.width = height; } } Now Square and Rectangle appear to work. No matter what you do to a Square object, it will remain consistent with a mathematical square, and regardless of what you do to a Rectangle object, it will remain a mathematical rectangle.
  78. public class Rectangle { … @Ensures({“getWidth() = width”}) public void

    setWidth(double width) { this.width = width; } … @Ensures({“getHeight() = height”}) public void setHeight(double height) { this.height = height; } … } @Invariant(“getWidth() = getHeight()”) public class Square extends Rectangle { @Ensures({“getWidth() = width”}) public void setWidth(double width) { this.width = width; this.height = width; } @Ensures({“getHeight() = height”}) public void setHeight(double height) { this.height = height; this.width = height; } } The invariant of the Square is now satisfied. The assertion now passes. public void f(Square square) { square.setWidth(5); assert square.getHeight() == 5; }
  79. public class Rectangle { … @Ensures({“getWidth() = width”}) public void

    setWidth(double width) { this.width = width; } … @Ensures({“getHeight() = height”}) public void setHeight(double height) { this.height = height; } … } So we might conclude that the design is now self-consistent and correct. @Invariant(“getWidth() = getHeight()”) public class Square extends Rectangle { @Ensures({“getWidth() = width”}) public void setWidth(double width) { this.width = width; this.height = width; } @Ensures({“getHeight() = height”}) public void setHeight(double height) { this.height = height; this.width = height; } } However, this conclusion would be amiss. A design that is self-consistent is not necessarily consistent with all its users!
  80. public class Rectangle { … @Ensures({“getWidth() = width”}) public void

    setWidth(double width) { this.width = width; } … @Ensures({“getHeight() = height”}) public void setHeight(double height) { this.height = height; } … } public void g(Rectangle rectangle) { rectangle.setWidth(5); rectangle.setHeight(4); assert rectangle.getArea() == 20; } Consider a client’s function g: @Invariant(“getWidth() = getHeight()”) public class Square extends Rectangle { @Ensures({“getWidth() = width”}) public void setWidth(double width) { this.width = width; this.height = width; } @Ensures({“getHeight() = height”}) public void setHeight(double height) { this.height = height; this.width = height; } }
  81. public class Rectangle { … @Ensures({“getWidth() = width”}) public void

    setWidth(double width) { this.width = width; } … @Ensures({“getHeight() = height”}) public void setHeight(double height) { this.height = height; } … } public void g(Rectangle rectangle) { rectangle.setWidth(5); rectangle.setHeight(4); assert rectangle.getArea() == 20; } This function sets the width and height of what it believes to be a Rectangle. @Invariant(“getWidth() = getHeight()”) public class Square extends Rectangle { @Ensures({“getWidth() = width”}) public void setWidth(double width) { this.width = width; this.height = width; } @Ensures({“getHeight() = height”}) public void setHeight(double height) { this.height = height; this.width = height; } } The function works fine for a Rectangle but fails if passed a Square. Problem: The author of g assumed that changing the width of a Rectangle leaves its height unchanged.
  82. @Invariant(“getWidth() = getHeight()”) public class Square extends Rectangle { @Ensures({“getWidth()

    = width”}) public void setWidth(double width) { this.width = width; this.height = width; } @Ensures({“getHeight() = height”}) public void setHeight(double height) { this.height = height; this.width = height; } } public class Rectangle { … @Ensures({“getWidth() = width”}) public void setWidth(double width) { this.width = width; } … @Ensures({“getHeight() = height”}) public void setHeight(double height) { this.height = height; } … } Clearly, it is reasonable to assume that changing the width of a rectangle does not affect its height! In fact it is so obviously right that we didn’t even bother explicitly adding it as a postcondition of Rectangle’s setters, we left it implicit! Let’s make it explicit.
  83. public class Rectangle { … @Ensures({ “getWidth() = width getHeight()

    = old(getHeight())”}) public void setWidth(double width) { this.width = width; } … @Ensures({ “getHeight() = height getWidth() = old(getWidth())”}) public void setHeight(double height) { this.height = height; } } Clearly, it is reasonable to assume that changing the width of a rectangle does not affect its height! @Invariant(“getWidth() = getHeight()”) public class Square extends Rectangle { @Ensures({“getWidth() = width”}) public void setWidth(double width) { this.width = width; this.height = width; } @Ensures({“getHeight() = height”}) public void setHeight(double height) { this.height = height; this.width = height; } } In fact it is so obviously right that we didn’t even bother explicitly adding it as a postcondition of Rectangle’s setters, we left it implicit! Let’s make it explicit.
  84. public class Rectangle { … @Ensures({ “getWidth() = width getHeight()

    = old(getHeight())”}) public void setWidth(double width) { this.width = width; } … @Ensures({ “getHeight() = height getWidth() = old(getWidth())”}) public void setHeight(double height) { this.height = height; } } @Invariant(“getWidth() = getHeight()”) public class Square extends Rectangle { @Ensures({“getWidth() = width”}) public void setWidth(double width) { this.width = width; this.height = width; } @Ensures({“getHeight() = height”}) public void setHeight(double height) { this.height = height; this.width = height; } } Clearly, the postcondition of Square’s setWidth is weaker than the postcondition of Rectangle’s setWidth, since it does not enforce the constraint (getHeight() = old(getHeight()). Similarly for Square’s setHeight. Square’s setWidth and setHeight methods violate the contract of the Rectangle base class.
  85. • Every operation’s precondition is no stronger than the corresponding

    operation in the superclass • Every operation’s postcondition is at least as strong as the corresponding operation in the superclass “methods must ‘behave like’ calls to the corresponding supertype methods” #2 The Methods Rule Square’s setters don’t behave like Rectangle’s Setters! The postconditions of Square’s setters are weaker than those of Rectangle’s setters! 1. Signature Rule 2. Methods Rule 3. Properties Rule Square Violates Rectangle’s contract
  86. It is often said that inheritance is the IS-A relationship.

    e.g. a square is a rectangle, therefore Square should be derived from the Rectangle. ISA If a new kind of object can be said to fulfill the IS-A relationship with an old kind of object, the class of the new object should be derived from the class of the old object. Flawed reasoning – recap
  87. But IS-A is about behaviour. In OOD, a square IS-A

    rectangle only if it behaves like a rectangle, if it honours Rectangle’s contract, if it satisfies the LSP. The contract may be implicit (not so good). A square is a rectangle, but only in a geometrical sense, not in a behavioural sense. In OOD, Square violates Rectangle’s contract, it violates the LSP, therefore it does not behave like a Rectangle: it is not true that a Square IS-A Rectangle and therefore Square should not inherit from Rectangle. IS-A __ X X It may be expressed in comments (better). It may be expressed as automated unit tests (much better). Or it may be expressed using a DbC framework (rare?).
  88. To adhere to LSP in Java, we must make sure

    that developers define preconditions and postconditions for each of the methods on an abstract class. X In order to take advantage of LSP, we must adhere to OCP because violations of LSP also are violations of OCP, but not vice versa. X X When defining our subclasses, we must adhere to these preconditions and postconditions. If we do not define preconditions and postconditions for our methods, it becomes virtually impossible to find violations of LSP. Kirk Knoernschild The Liskov Substitution Principle is one of the prime enablers of OCP. We can think of the Liskov Substitution Principle (LSP) as an extension to OCP. 2002
  89. When interacting with these awkward objects, programmers are forced to

    know their quirks and into dependencies that are better avoided. when asked to use one they will embed knowledge of its quirks into their own code if (bicycle instanceof MountainBike) { // do XYZ } if (bicycle instanceof MountainBike) { // do XYZ } if (bicycle instanceof MountainBike) { // code that knows about } often by explicitly checking the classes of objects. Recap: Sandi on consequences of untrustworthy Hierarchies
  90. every violation of the LSP is a latent violation of

    the OCP X X because in order to repair the damage … we are going to have to add if statements and hang dependencies upon subtypes if (bicycle instanceof MountainBike) { // do XYZ } if (bicycle instanceof MountainBike) { // do XYZ } if (bicycle instanceof MountainBike) { // code that knows about } Uncle Bob on violations of LSP
  91. Next time you are faced with adding logic to a

    non-trivial conditional, don’t just follow the pattern and add another branch. See if the conditional is one of those that can be refactored to the OCP, using inheritance if necessary. Next time you consider introducing an inheritance hierarchy, ask yourself if it meets Sandi’s criteria for using inheritance. If not, consider the consequences of going ahead (if you decide to do so).
  92. Next time you create a base class, spend some time

    thinking about how its contract needs to be expressed. Is it really acceptable to leave it implicit? Is it so obvious? Is it workable and effective to express the contract using unit tests? If not, maybe explore the possibility of using a DbC framework. If not, express the contract using unit tests.
  93. Next time you create a derived class, verify that it

    satisfies the contract of its superclass. Next time you come across instanceof usages, see if they are the smell of an untrustworthy hierarchy. If it doesn’t, take remedial action. If so, fix the hierarchy.
  94. References All images sourced from http://www.google.co.uk/advanced_image_search, so see there for

    details of which are subject to copyright Practical Object-Oriented Design in Ruby: An Agile Primer – by Sandi Metz | Publication Date: September 15, 2012 | ISBN-10: 0321721330 | ISBN-13: 978- 0321721334 Agile Software Development, Principles, Patterns, and Practices – by Robert C. Martin | Publication Date: October 25, 2002 | ISBN-10: 0135974445 | ISBN-13: 978- 0135974445 Object-Oriented Software Construction – by Bertrand Meyer | Publication Date: 3 April 1997 | ISBN-10: 0136291554 | ISBN-13: 978-0136291558 | Edition: 2 Program Development in Java – Abstraction, Specification and OO Design – by Barbara Liskov, with John Guttag | Publication Date: 6 Jun 2000 | ISBN-10: 0201657686 | ISBN-13: 978-0201657685
  95. Java Design: Objects, UML, and Process – by Kirk Knoernschild

    | Publication Date: December 18, 2001 | ISBN-10: 0201750449 | ISBN-13: 978-0201750447 The Coding Dojo Handbook - a practical guide to creating a space where good programmers can become great programmers – by Emily Bache | Publication Date: 29 October 2013 | https://leanpub.com/codingdojohandbook Fundamentals of Object Oriented Design in UML – by Meilir Page-Jones | Publication Date: November 13, 1999 | ISBN-13: 978-0201699463 ISBN-10: 020169946X The Anti-IF Campaign - http://antiifcampaign.com/ The Gilded Rose Kata - http://iamnotmyself.com/2011/02/13/refactor-this-the-gilded-rose-kata/ References (continued)