250 likes | 268 Views
Learn about Agile Design principles such as SRP, OCP, LSP, DIP, and ISP. Understand how to refactor designs to be flexible, extensible, and resilient. Examples and explanations provided.
E N D
Agile Design What is Agile Design? SRP OCP LSP DIP ISP
Agile Design • What is Agile Design? • SRP • OCP • LSP • DIP • ISP
What is Agile Design? • Design is the structure of the system, modules, classes, methods • Some aspects of it may be illustrated in UML diagrams • Design commonly degrades as the system is changed • Requirements do change, so the system has to change • Initially sound design rots to pieces • Result suffers from code smells • Rigidity, Fragility, Immobility, Viscosity, Needless Complexity (YAGNI), Needless Repetition (DRY), Opacity • Changes are often made simple instead of design preserving (opacity) • Example: Copy program • Cluttered with fixes instead of refactored to Open-Closed Principle
The Copy Program • Original program • Copy from keyboard to printer • void Copy(){ int c; while ((c=RdKbd()) != EOF) WrtPrt(c);} • Change requirements • Allow input to be taken from paper tape as well • Allow output to go to the paper punch as well
The Modified Copy Program • The modified program • bool ptFlag = false;bool punchFlag = false;// remember to reset these flagsvoid Copy(){ int c; while ((c=(ptFlag ? RdPt() : RdKbd()) != EOF) punchFlag ? WrtPunch(c) : WrtPrt(c);} • Design flaws • Rigid (changes ripple), Fragile (breaks easily), Immobile (hard to reuse parts), Complex (all logic entangled in main loop), Redundant (replicated logic), Opaque (hard to understand) • Modified requirements squeezed into original design
The Refactored Copy Program • Refactoring to design standards (after first requirement change) • public abstract class Reader{ abstract int read();}public class KeyboardReader : Reader{ override int read(){ return RdKbd(); }}KeyboardReader defaultReader = new KeyboardReader();void Copy() { Copy(defaultReader);}void Copy(Reader reader){ int c; while ((c=reader.read()) != EOF) WrtPrt(c);}
Principles and Patterns in Refactored Copy Program • Open-Closed Principle (OCP) • New input devices do not require source code changes any more • Solution is open for extensions, but closed for modifications • With respect to changes in input devices • Dependency Inversion Principle (DIP) • In original design, main module (policy) was dependent on low-level details • Made it sensitive to changes in submodules (policy implementations) • After refactoring, submodules are dependent on higher-level interface • Main module uses same interface, insensible to implementation changes • Strategy Design Pattern • Detailed low-level implementation supplied to main module from the outside • Allows change in behaviour without main module caring about details • Avoid Needless Complexity (YAGNI) • Wait as long as possible before refactoring • Until there is a need to change output devices, don’t make a solution for it
Agile Design • What is Agile Design? • The Single Responsibility Principle, SRP • OCP • LSP • DIP • ISP
A Class should represent One and only One Concept • A Class should represent One and only One Concept • It should be Coherent (all elements functionally related) • It should have only one Reason to Change • Anti-example: Bowling Game • Responsible of both current frame and of calculating score • Separate in Game to keep track of frames, and Scorer to calculate score • Each responsibility is an axis of change • Separating them allows changes to be made for fewer reasons • Allows for less fragile design • Only if that change actually occurs • Typical mixtures of responsibilities • Logic and user input/output, or logic and persistence control • Change at different rates and for different reasons and should not be mixed
Agile Design • What is Agile Design? • SRP • The Open-Closed Principle, OCP • LSP • DIP • ISP
Open for Extension, Closed for Modification • Software entities (classes, modules, functions, etc.) should be • Open for extension • It is possible to add new behaviour to it • Closed for modification • Extension does not cause modifications in source or binary code • Make client modules work with abstractions • Not implementations • But only when there actually is a change (YAGNI) • Resisting premature abstraction is as important as abstraction itself • You may stimulate or anticipate (YAGNI risk!) the change • Abstractions may be • Abstract classes or interfaces • Abstract methods, Template Method (”Hollywood”) Design Pattern • Implemented in subclasses
Example in C#: IComparer • In C#, the generic class List<T> has a static method Sort • aList.Sort(aComparer) • aComparer class implements IComparer<T> • Sort compares pairs of elements in the list using the comparer • if (aComparer.Compare(e1, e2) > 0) Swap(e1, e2); • Compare returns a negative, zero or positive number indicating sorting order • The comparer knows about sorting order of elements of type T • The list does not • The class List<T> is • Open for extension of new sorting orders • Closed for modification with regard to such extensions
Agile Design • What is Agile Design? • SRP • OCP • The Liskov Substitution Principle, LSP • DIP • ISP
How to apply inheritance and conform to the OCP? • Subtypes must be substitutable for their base types • public class B{ virtual void f(){...}}public class D : B{ override void f(){...}} • In the client: • B anObject = new D();anObject.f(); • The client will behave as if it had a B object • So f in D must behave as f in B, or the client will be confused
Counter-example: Square ”is a” Rectangle • Square is a rectangle • public class Square : Rectangle {...} • public Rectangle GimmeAShape(){...}// returns either • Rectangle client getting a rectangle • Rectangle r = GimmeAShape(); // gets a rectangler.setWidth(3); r.setHeight(4);if (12 != r.getArea()) driveMeNuts(); // puh, ok • Square client getting a square • Rectangle r = GimmeAShape(); // gets a squarer.setWidth(3); r.setHeight(4);if (12 != r.getArea()) driveMeNuts(); // arghhh, 16! • A square does not behave like a rectangle • So the client gets confused when square says it is a rectangle
LSP, Design by Contract, and TDD • A subclass obeys the LSP if, for all overriding methods • The precondition is the same or weaker than in the base class • The postcondition is the same or stronger than in the base class • For all cases that satisfy the original precondition from the base class • Or, said differently, the contract is the same or weaker than in the base class • A test case for a base class method should run unchanged for a subclass override • The input conditions should satisfy the override • The override should satisfy all the outcome expectations • The collection of test cases implicitly defines the contract
LSP Example: Stack • Abstract class Stack, with UnboundedStack, BoundedStack • public abstract class Stack { public void push(Element e) {...} // Pre: !IsFull() abstract bool IsFull();} • public class UnboundedStack : Stack { override bool IsFull() {return false;}} • public class BoundedStack : Stack { override bool IsFull() {return this.spaceLeft() == 0;}} • Client code is independent of which kind of stack it uses • Stack s = GimmeAStack(); // may return eitherif (!s.IsFull()) s.push(new Element()); // works with either
Agile Design • What is Agile Design? • SRP • OCP • LSP • The Dependency Inversion Principle, DIP • ISP
Depend on Abstractions, Not on Details • High-level modules should not depend on low-level modules • Both should depend on abstractions • Abstractions should not depend on details • Details should depent on abstractions • Define an interface for services needed by a higher (policy) layer • Let lower (mechanism) layers implement that interface • Invert top-down dependency to bottom-up • If lower-layer classes are likely to change (beware of YAGNI) • The upper layer client needs to own the interface • Breaks dependency on lower, implementation layers • Makes the upper layer module more reusable
DIP Example: Light Switch • Button, when pressed, turns on or off a lamp • public class Button{ private Lamp lamp = new Lamp(); public void Poll(){ if (/*some condition*/) lamp.On(); else lamp.Off(); }} • Button directly depends on lamp • Cannot be used with any other device
Invert Depencencies • Reuse design for other devices, like motors • public interface SwitchableDevice{ void On(); void Off();} • Make both button and lamp depend on SwitchableDevice • public class Lamp : SwitchableDevice{ // implement On and Off} • public class Button{ private SwitchableDevice device; public Button(SwitchableDevice device) {...} public void Poll(){ if (/*some condition*/) device.On(); }} • Both button and SwitchableDevice are separately reusable
Agile Design • What is Agile Design? • SRP • OCP • LSP • DIP • The Interface-Segregation Principle, ISP
Clients should Know about Cohesive Interfaces • Fat classes cause bizarre and harmful couplings between their clients • When one client forces a change on the interface, all the others are affected • Avoid accumulated, fat interfaces • A client should not be forced to depend on methods that it does not use • Should only relate to methods it uses itself • A class chould not be forced to be recompiled • Because some other class causes a change in the interface unrelated to our class • Separate fat interfaces in cohesive blocks • Separation through delegation: Using an object adapter • Separation through Multiple Inheritance