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.
Look for definitions in ISO 9126 Projects sometimes fail due to not having any clear definitions of "success” This standard tries to develop a common understanding of project objectives and goals, e.g. software qualities
can • diagnose it for § deficiencies § causes of failure • identify the parts to be modified “Analysability is basically the ability to understand software”
allows you to • implement a specific modification • add, modify, or enhance a feature at a reasonable cost “almost all Design Patterns are geared towards increasing design’s changeability”
can add/enhance functionality purely by adding software units and specifically not by modifying existing software units” Flexibility a special case of changeability
production code Changeability is a desirable quality, but it relies on Change by Modification Change by Modification “The less I ever modify a class, the higher the probability that it will remain free of defects” Modifications carry the risk of introducing defects, and the necessary cost of avoiding them: testing, code reviewing, etc.
code Change by Modification Change by Addition Behavioural changes that are introduced by adding new production code instead of modifying existing code Contrast Change by Addition avoids (risky) modifications altogether with
does not take a stand point with regards to the way a specified modification is implemented Flexibility In contrast, Flexibility does take this stand point and requires that no modifications are made
avoids unexpected effects when modified “I advocate the practice to avoid modifying existing code but preferably add features or modify existing ones by other means” Flexibility “any change to existing software carries a risk of introducing defects”
if you require that s/w can adapt to changing requirements without modifying the production code then you need to employ a SPECIAL set of design and programming techniques as well as adopt a SPECIAL mindset
it is still available for extension or add fields to its data structures Data Structures fields + operations e.g. it should be possible to expand its set of operations
it is available for use by other modules Has a well-‐defined, stable description (its interface – in information hiding sense) public part secret part interface 1 can be compiled, stored in a library, and made available for clients to use 2 in the case of a design or specification module: • approved • baselined in version control • its interface published for benefit of other module authors 3
a module will need in its lifetime X The need for ness so developers wish to keep the module open for as long as possible so that they can address changes, and extensions by changing elements or adding new elements
never closed until it is certain that it contains all the needed features x every developer would always be waiting for completion of another developer's job then multi-‐module s/w can never reach completion x in a system consisting of many modules, most modules will depend on some others but it is also necessary to close modules
use it yet and any change or extension can trigger a painful chain reaction of changes or you close it in many other modules which relied on the original module directly or indirectly
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
variants of the original module, many of them very similar, but never quite identical if you extrapolate its effects to • many modules • many modification requests • a long period of time
add/modify logic you have to: • Do it for each source tree • Write the same test cases for each source tree you have to do it in each source tree when you need to remove a defect
evolve into completely different directions: they drift apart. After a while it is more or less like maintaining a set of completely different applications At that point, before you do any of the operations, you have to first analyse each source tree!!
We have modified A into A+, which can switch between two modes of execution In one mode it behaves like A, and in the other it behaves as expected of A’ Solution
We have modified A into A+, which can switch between two modes of execution In one mode it behaves like A, and in the other it behaves as expected of A’ Solution
if (variant == VARIANT_1) then { …. } else { …. } At points of variation, A+ looks like this: We have modified A into A+, which can switch between two modes of execution In one mode it behaves like A, and in the other it behaves as expected of A’ Alternatively, this can be a switch Solution
The potential for disaster is obvious: changes to A may invalidate the assumptions on the basis of which the old clients used A. So the changes may start a dramatic series of changes in clients, client of clients....etc Solution – Meyer’s Assessment
The potential for disaster is obvious: changes to A may invalidate the assumptions on the basis of which the old clients used A. So the changes may start a dramatic series of changes in clients, client of clients....etc B C E D this is a nightmare for the proj. mgr. the system regresses and several modules have to be re-‐ opened for dev/test/debug/documentation
effect, it is still better than the Copy solution. On the surface, the copy solution seems better because it avoids the ripple effect of change but in fact it may even be more catastrophic…it only postpones the day of reckoning We saw earlier the risks of an explosion of variants, many of them very similar, but never quite identical: Solution – Meyer’s Assessment solution solution
by Modification Reliability Concerns – solution relies on with risk of introducing new defects Analysability concerns – as more and more requirements are handled by parameter switching, the code becomes less easy to analyse … Responsibility erosion – the software has, without much notice, been given an extra responsibility drives towards Procedural Design Blob aka God Class Solution (Parametric solution) Christensen’s Assessment
I like to call this switch The flow of the switches themselves becomes confusing, hard to read, hard to decipher. When a new case comes in the programmer must find every place it can be involved (often finding all but one of them). Suddenly things get bad in a hurry.
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
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?
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
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
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
different hackers, the software starts resembling a chunk of Swiss cheese that has been left outside for too long in August – it has both holes and growth Hacking
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
adaptation of healthy modules If there is something wrong with a module you should fix it… …not leave the original alone and try to correct the problem in the derived module Derived Base neither OCP nor redefinition in inheritance is a way to address design flaws, let alone bugs Design Flaw
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 2004
and operation implementation) It's important to understand the difference between an object's class and its type An object's type only refers to its interface -‐ the set of requests to which it can respond
type. Because a class defines the operations it can perform, it also defines the object's type. public class Person { private String name; private Date birthdate; public Person(String name, Date birthdate) { this.name = name; this.birthdate = birthdate } public String getName() { return name; } public String getBirthdate() { return birthdate ; } } Any class C, implicitly forms a type C. Languages like C++ and Eiffel use classes to specify BOTH an object's type AND it's implementation.
general description, used for inheritance and polymorphic type declarations Realization: a relationship between a specification and its implementation Derived inherits implementation from Base Derived inherits type (interface) from Base Derived inherits type from Base Current and future specializations of Base are substitutable for Base in clients Current and future realizations are substitutable for Base in clients “[in a Type hierarchy] the supertype’s behavior must be supported by the subtypes: subtype objects can be substituted for supertypeobjects without affecting the behavior of the using code.” Liskov Substitution Principle (Barbara Liskov – 1988)
inheritance and interface inheritance (or subtyping) In contrast, interface inheritance (or subtyping) describes when an object can be used in place of another Class inheritance defines an object's implementation in terms of another object's implementation. In short, it's a mechanism for code and representation sharing Class Inheritance AND Interface Inheritance JUST Interface Inheritance
languages don't support the distinction between [them] In languages like C++ and Eiffel, inheritance means BOTH interface inheritance and implementation inheritance [ interface inheritance and implementation inheritance]
inheritance and subtyping into a single mechanism. That's a kludge. Interfaces decouple the two concerns – Nat Pryce The interface construct is one the few things that Java really got right (that and GC) – Steve Freeman But Java improves on C++ by also supporting realization with implements Class Inheritance AND Interface Inheritance JUSTInterface Inheritance JUSTInterface Inheritance
java over again, what would you change?" His answer: "I'd leave out classes" After the laughter died down, he explained that the real problem wasn't classes per se but rather implementation inheritance (the extends relationship). Class Inheritance AND Interface Inheritance JUST Interface Inheritance JUST Interface Inheritance Interface inheritance (the implements relationship) is much preferred. Avoid implementation inheritance whenever possible.
between interface [inheritance] and implementation inheritance, people make the distinction in practice Many of the design patterns Depend on this distinction
replacing implementation inheritance (extends) with interface inheritance (implements) Allen Holub 2004 It's not an accident that there are many more Object patterns than Class patterns. implementation inheritance interface inheritance
Principle Liskov Substitution Principle Interface Segregation Principle Dependency Inversion Principle Rigidity. The design is difficult to change. Fragility. The design is easy to break. Immobility. The design is difficult to reuse. Viscosity. It is difficult to do the right thing. Needless complexity. Overdesign. Needless repetition. Mouse abuse. Opacity. Disorganized expression. Symptoms of poor design (or Design Smells): Often, the smell is caused by the violation of one or more OO Design Principles: OO Design Principles help developers eliminate Design Smells The Contemporary Version of the OCP
Contemporary Version of the OCP Robert Martin (Uncle Bob) Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. • They are open for extension. This means that the behavior of the module can be extended. As the requirements of the application change, we can extend the module with new behaviors that satisfy those changes. In other words, we are able to change what the module does. • They are closed for modification. Extending the behavior of a module does not result in changes to the source, or binary, code of the module. The binary executable version of the module…remains untouched.
concrete class or class that can be instantiated. Instead, the class is coupled to other base, or abstract, classes. Kirk Knoernschild Account class is coupled at the abstract level to the AccountType inheritance hierarchy So we can extend the AccountType class, creating a new class such as MoneyMarket, without having to modify our Account class. We have achieved OCP and now can extend our system without modifying its existing code base. Account isn't directly coupled to either of the concrete Savings or Checking classes 2001 In Java, this abstract class can be either a class with the abstract modifier or a Java interface data type. At the heart of the contemporary OCP there is the concept of abstract coupling.
the concept of abstract coupling and clearly states that we should couple at the abstract level, not at the concrete level • There exists a striking similarity between DIP and OCP. In fact, these two principles are closely related • High-level modules should not depend on low-level modules. Both should depend on abstractions. DIP: The Dependency-Inversion Principle • Abstractions should not depend on details. Details should depend on abstractions. • Fundamentally, DIP tells us how we can adhere to OCP • if OCP is the desired end, DIP is the means through which we achieve that end.
AccountType is abstract coupling, and so is the coupling of Savings and Checking to AccountType High-‐level module Account does not depend on low-‐level modules Savings and Checking. Account, Savings and Checking, all depend on an abstraction: AccountType. DIP
of LSP as an extension to OCP The LSP is one of the prime enablers of OCP 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 But why?
the OCP because in order to repair the damage … we are going to have to add if statements and hang dependencies upon subtypes Untrustworthy hierarchies [those violating the LSP] force objects that interact with them to know their quirks when asked to use one … [inexperienced developers] 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 X X Change by Modification
OCP, but a subtle difference does exist. Savings and Checking are substitutable for AccountType OCP is centered around abstract coupling. LSP, while also heavily dependent on abstract coupling, is in addition heavily dependent on preconditions and postconditions, which is LSP's relation to Design by Contract LSP
common ways of satisfying OCP Template Method Pattern Strategy Pattern Is one as good as the other? Can they be used interchangeably? interface inheritance implementation inheritance
because many languages don’t support the distinction between them” “The GoF Design Patterns book is, in fact, largely about replacing implementation inheritance (extends) with interface inheritance (implements)” “Template Method has little to recommend it in most situations. Strategy for example, typically provides a better alternative.” “Many of the design patterns Depend on this distinction” Interface inheritance (the implements relationship) is much preferred. Avoid implementation inheritance whenever possible.
may come across is the Open Closed Principle” “Meyer is generally credited as having originated the term [OCP], however his focus (being in the golden days of OO inheritance) was on the polymorphic approach” Template Method Pattern Polymorphic solution Change by Addition OCP solution Organized Hacking CHANGE solution COPY solution Source code copy solution Parametric solution multiple maintenance problem Change by Modification Strategy Pattern Compositional solution You encapsulatethe variability points in instance methods. These can then be overridden in subclasses, one for each required variant You encapsulate the variability points in a well defined interface and use delegation to compose the overall behaviour. Concrete classes, implementing the interface, define the variants’ behaviour. Change by Addition Change by Addition
easy to overuse and that overuse of inheritance was very costly. [The Gang of Four] went so far as to stress: [Template Method and Strategy are] two patterns that epitomize the difference between inheritance and delegation. They solve similar problems and can often be used interchangeably So we cut back on our use of [implementation] inheritance, often replacing it with composition or delegation. “Favourobject composition over class inheritance” Prevents us from making one of the most catastrophic mistakes that contribute to the demise of an object-‐oriented system: using inheritance as the primary reuse mechanism The Composite Reuse Principle
from the detailed implementation of that functionality”. The STRATEGYpattern provides one extra benefit over the TEMPLATE METHODpattern. In order to conform to the DIP, we want to make sure that the generic algorithm does not depend on the detailed implementation. Strategy Pattern Template Method Pattern
many possible detailed implementations, But Template Method partially violates the DIP because it uses implementation inheritance so the detailed implementations don't depend on an abstraction they depend on the generic algorithm and so they are inextricably bound to it and cannot be reused by other generic algorithms High-‐level modules should not depend on low-‐level modules. Both should depend on abstractions. BubbleSorter’s doSort() method satisfies the DIP because it depends on abstract methods outOfOrder() and swap() DoubleBubbleSorter and IntegerBubbleSorter do not satisfy the DIP because they depend on BubbleSorter, which is NOT an abstraction since it contains a concrete generic algorithm DIP X Design Smell: Immobility Template Method Pattern
uses interface inheritance so the detailed implementations do depend on an abstraction(the interface), so the detailed implementations can be manipulated by (reused for) many different generic algorithms Strategy has this additional benefit over Template Method High-‐level modules should not depend on low-‐level modules. Both should depend on abstractions. DIP Not only does BubbleSorter satisfy the DIP, because its sort() method depends on interface SortHandler, i.e. an abstraction but IntegerSortHandler and DoublSortHandler also satisfy the DIP, because they also depend on the SortHandler abstraction Strategy Pattern
the OCP used implementation inheritance While the contemporary version of the OCP mostly uses interface inheritance, it sometimes does use implementation inheritance realization Contemporary OCP generalization +
object-‐oriented design Conformance to this principle is what yields the greatest benefits claimed for OO technology: flexibility, reusability, and maintainability [it is not] a good idea to apply rampant abstraction to every part of the application. Rather, it requires a dedication on the part of the developers to apply abstraction only to those parts of the program that exhibit frequent change. Resisting premature abstraction is as important as abstraction itself.
details of which are subject to copyright Object-‐Oriented Software Construction – by Bertrand Meyer; Publication Date: 3 April 1997 | ISBN-‐10: 0136291554 | ISBN-‐13: 978-‐0136291558 | Edition: 2 Flexible, Reliable Software: Using Patterns and Agile Development – by Henrik B. Christensen; Publication Date: 11 May 2010 | ISBN-‐10: 1420093622 | ISBN-‐13: 978-‐1420093629 Design Patterns Explained: A New Perspective on Object-‐Oriented Design; by Alan Shalloway; Publication Date: 12 Oct 2004 | ISBN-‐10: 0321247140 | ISBN-‐13: 978-‐0321247148 | Edition: 2 Less -‐ The Path to Better Design; by Sandi Metz at GoRuCo 2011 goruco_2011_-‐_sandi_metz_-‐_less_-‐ _the_path_to_better_design_1280x720.mp4 Why Extends is Evil -‐ Improve your code by replacing concrete base classes with interfaces; by Allen Holub; http://www.javaworld.com/article/2073649/core-‐java/why-‐extends-‐is-‐evil.html