Object Oriented Design Principles
Arnon Rotem-Gal-Oz Product Line Architect
Preface Nothing really new here Just a summary of other people’s work: D.L. Parnas M. Fowler R.C. Martin, B. Meyer, B. Liskov W.J. Brown, R.C. Malveau, H.W. McCormick, T.J. Mowbray GoF N. Malik A. Holub And probably others I forgot
Agenda 7 Deadly Sins of Design The Rules Basic Stuff Evil Stuff More Stuff
7 Deadly Sins of Design Rigidity – make it hard to change Fragility – make it easy to break Immobility – make it hard to reuse Viscosity – make it hard to do the right thing Needless Complexity – over design Needless Repetition – error prone Not doing any
The Rules Apply Common Sense Don’t get too dogmatic / religious Every decision is a tradeoff All other principles are just that Guidelines “best practices” Consider carefully if you should violate them - but, know you can.
Basic Stuff OCP open-closed principle SRP single responsibility principle ISP interface segregation principle LSP Liskov substitution principle DIP dependency inversion principle Dependency Injection
Open Closed Principle Software entities ( Classes, Modules, Methods, etc. ) should be open for extension, but closed for modification. Also Known As Protected Variation What Parnas meant when he coined “Information hiding”
Why OCP If not followed a single change to a program results in a cascade of changes to dependent modules. The program becomes fragile, rigid, unpredictable and un-reusable.
OCP Example
Template Method
OCP implications (examples) Use private modifier for class members Refrain from adding Getters automatically Use abstractions Extend by inheritance, delegation
Inversion of Control (IoC)
Single Responsibility Principle A Class should have one reason to change A Responsibility is a reasons to change
Can be tricky to get granularity right
Why SRP Single Responsibility = increased cohesion Not following results in needless dependencies More reasons to change. Rigidity, Immobility
SRP Example (1/2) The Rectangle has 2 responsibilities Algorithm
Rectangle Draw() Area()
Client Application
DirectX Dependency
Suddenly the Algorithm needs DirectX
SRP Example (2/2)
Rectangle Area()
Algorithm
Rectangle Renderer Draw()
Client Application
DirectX Dependency
Interface Segregation Principle Many client specific interfaces are better than one general purpose interface
Create an interface per client type not per client Avoid needless coupling to clients
Why ISP Otherwise – increased coupling between different clients Basically a variation on SRP
ISP example IRectangle Area()
IRectangle Renderer Draw() Rectangle Impl.
Algorithm
DirectX
Client Application
Realization Dependency
Liskov Substitution Principle “What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.” (Barbara Liskov, 1988)
Or in English Any subclass should always be usable instead of its parent class.
Corollary - All derived classes must honour the contracts of their base classes IS A = same public behavior Pre-conditions can only get weaker Post-conditions can only get stronger
Why LSP Failing to follow LSP result in a mess OCP violations (if/then to identify types) Superclass unit test will fail Strange behavior
Trivial Example cd Diagram Examples Rectangle + +
SetHight() : void SetWidth() : void
Square + +
SetHight() : void SetWidth() : void
Why is that an LSP violation?
Real-Life Example cd Diagram Examples
cd Diagram Examples «interface» IContributeObjectSink
MarshalByRefObject
{abstract} +
ContextBoundObject
GetObjectSink (MarshalByRefObject, IMasshageSink ) : IMessageSink {abstract}
.NET creates a transparent proxy and intercepts all calls to ContextBoundObjects Implementing IContextObjectSink On a class derived from ContrextAttribute lets you add a sink in the chain
Real-Life Example cd Diagram Examples MarshalByRefObject
ContextBoundObject
ServicedComponent
ServicedComponents violates LSP: You cannot add your own sinks (it only uses its own)
Dependency Inversion Principle Higher level modules should not depend on lower level modules Both should depend on abstractions Interfaces or Abstract classes
Abstractions should not depend on details (Not to be confused with Dependency Injection and Inversion of Control)
Why DIP Increase loose coupling Abstract interfaces don't change Concrete classes implement interfaces Concrete classes easy to throw away and replace
Increase mobility Increase isolation decrease rigidity Increase testability Increase maintainablity
Closely related to LSP
Reminder - Procedural Design cd Diagram Examples :Program
:Module
:Function
:Module
:Function
:Module
:Function
:Function
DIP cd Diagram Examples :Program
«interface» :Interface1
«realize»
:Class1
«interface» :Interface2
«realize»
:Class2
«interface» :Interface3
«realize»
:Class3
«realize»
:Class4
DIP implications Layers Interface based programming Separated Interface put interface in separate package than implementation
Dependency Injection
Old Way public class MyApp { public MyApp() { authenticator = new Authenticator(); database = new Database(); logger = new Logger(); errorHandler = new ErrorHandler(); } // More code here...
}
Dependency Injection DIP says we should depend on an interface how do we get the concrete instance
New Way? public class MyApp { public MyApp() { authenticator = new IAuthenticator(); database = new IDatabase(); logger = new ILogger(); errorHandler = new IErrorHandler(); }
S P O O
// More code here...
}
Dependency Injection Option 1 – Factory User depends on factory Factory depends on destination
Option 2 – Locator/Registry/Directory The component still controls the wiring Instantiation Sequence Dependency on the Locator
Option 3 – Dependency Injection An assembler controls the wiring
DI Options Setter Injection The component is passive Someone injects the dependency
DI Options Setter Injection Constructor Injection “Always” initialized Better dependency visibility
Other DI Options Interface Injection Variation on setter injection
Getter Injection Needs AOP (not clean)
Evil Stuff Switch statements If (type()) Singletons / Global variables Getters Helper Classes
More Stuff Package principles Not the core of this presentation
Smells Anti-Patterns
Package Principles Reuse-Release Equivalency Principle Common Closure Principle Common Reuse Principle Acyclic Dependencies Principle Stable Dependencies Principle Stable Abstractions Principle
CodeSmells Something that's quick to spot Indication for a possible problem Not always the problem it self May not be a problem at all
CodeSmell example – Long Method
DesignSmell (1) Many CodeSmells can also apply to design Long Parameter List Large Class (Swiss Army knife / Blob) Type Embedded in Name Uncommunicative Name Data Class Refused Bequest (LSP violations)
DesignSmell (2) Inappropriate Intimacy Lazy Class Feature Envy (Managers) Shotgun Surgery Parallel Inheritance Hierarchies Message Chains Component Without Interface Singletons
Design Related Anti-Patterns The Blob Functional Decomposition Poltergeist Golden Hammer Swiss Army Knife Kevorkian Component (Dead End) (Other Anti-Patterns deal with Architecture, Management, Code)
Thank you… Arnon Rotem-Gal-Oz
[email protected]