Class Design Principles
Class Design Principles
Programming Techniques
2
Class Design Principles
3
Class Design Principles: About Class Design Principles (CDPs)
4
Class Design Principles: About Class Design Principles (CDPs)
During its lifetime of a software its class design changes constantly.
This is a consequence of requirement changes which is the rationale for
conducting an iterative design process.
CDPs do not only judge the current state of the code
They give an understanding of how well the code will work
under the effect of change.
Especially whether and how changes will affect client classes.
5
Class Design Principles: About Class Design Principles (CDPs)
S.O.L.I.D. Principles
6
Class Design Principles
8
Class Design Principles: Single Responsibility Principle (SRP)
A class with only one responsibility will have only one reason to change!
9
Class Design Principles: Single Responsibility Principle (SRP) - Responsibility and Cohesion
In a class with very high cohesion every element is part of the
implementation of one concept.
The elements of the class work together to achieve one common
functionality.
A class with high cohesion implements only one responsibility (only few
responsibilities)!
Therefore, a class with low cohesion violates SRP.
10
Class Design Principles: Single Responsibility Principle (SRP)
GUI package uses Rectangle to draw rectangle shapes in the screen.
Rectangle uses DrawingUtility to implement draw.
GeometricApplication is a package for geometrical computations
which also uses Rectangle (area()).
What do you think
about the design?
11
Class Design Principles: Single Responsibility Principle (SRP) - Introduction to SRP by Example
Problems of Rectangle
12
Class Design Principles: Single Responsibility Principle (SRP) - Introduction to SRP by Example
Problems of Rectangle
13
Class Design Principles: Single Responsibility Principle (SRP) - Introduction to SRP by Example
14
Class Design Principles: Single Responsibility Principle (SRP) - Introduction to SRP by Example
A SRP-Compliant Design
Split Rectangle according to its responsibilities.
GeometricRectangle models a rectangle by its geometric properties.
DrawableRectangle models a graphical rectangle by its visual properties.
16
Class Design Principles: Single Responsibility Principle (SRP) - Introduction to SRP by Example
Takeaway so Far
17
Class Design Principles: Single Responsibility Principle (SRP) - Introduction to SRP by Example
What’s Next?
Next, more scenarios are discussed, where we might want to apply SRP.
Goal:
Get a better feeling as when to apply SRP and when not.
Get to know some issues related to applying SRP in terms of the
mechanisms available for doing so.
18
Class Design Principles: Single Responsibility Principle (SRP)
19
Class Design Principles: Single Responsibility Principle (SRP) - The Employee Example
Mixing business rules and technical aspects is calling for trouble!
From experience we know that both aspects are extremely volatile.
20
Class Design Principles: Single Responsibility Principle (SRP)
It depends…
21
Class Design Principles: Single Responsibility Principle (SRP) - The Modem Example
22
Class Design Principles: Single Responsibility Principle (SRP) - The Modem Example
23
Class Design Principles: Single Responsibility Principle (SRP) - The Modem Example
Probably not …
24
Class Design Principles: Single Responsibility Principle (SRP)
25
Class Design Principles: Single Responsibility Principle (SRP)
More generally, the SRP applies to any kind of programming abstraction
Classes, methods, functions, packages, data types, …
Important: The SRP must be applied with respect to the right level of
abstraction
High-level abstractions à high-level responsibilites
Otherwise it seems contradictory that, say, a package (a collection of
classes designed according to SRP) can have only a single responsibiltiy
26
Class Design Principles: Single Responsibility Principle (SRP) - SRP, more generally
Strategic Application
27
Class Design Principles: Single Responsibility Principle (SRP) - SRP, more generally
Simulate Change
28
Class Design Principles: Single Responsibility Principle (SRP)
29
Class Design Principles: Single Responsibility Principle (SRP) - The Smart Home Example
Typical OO Design
30
Class Design Principles: Single Responsibility Principle (SRP) - The Smart Home Example
31
Class Design Principles: Single Responsibility Principle (SRP) - The Smart Home Example
Base configuration
abstract class Location {
}
abstract class CompositeLocation extends Location {
abstract List<? extends Location> locations();
}
class Room extends Location {
}
class Floor extends CompositeLocation {
List<Room> rooms;
List<? extends Location> locations() { return rooms; }
}
class House extends CompositeLocation {
List<Floor> floors;
List<? extends Location> locations() { return floors; }
}
...
House house = new House();
House house() { return house; }
...
32
Class Design Principles: Single Responsibility Principle (SRP) - The Smart Home Example
Base configuration
abstract class Location {
}
Shutter
abstract control
class CompositeLocation extends Location {
abstract List<? extends Location> locations();
} abstract class Location {
LightingList<Shutter>
class Roomabstract
extends control{
Location shutters();
} }
abstract
class Floor extends class Location {{
CompositeLocation
abstractrooms;
List<Room> class CompositeLocation
abstract {
List<Light> lights();
List<? List<Shutter>
}
extends shutters()
Location> { ... {} return rooms; }
locations()
} }
abstract
class House extends class CompositeLocation
CompositeLocation { {
class Room
List<Floor> {
List<Light>
floors; lights() { ... }
List<? List<Shutter>
}
extends shutters;
Location> locations() { return floors; }
} List<Shutter> shutters() { return shutters; }
} class Room {
... List<Light> lights;
House house = new List<Light>
House(); lights() { return lights; }
House house() {} return house; }
...
33
Class Design Principles: Single Responsibility Principle (SRP) - The Smart Home Example
View-Specific Responsibilities
34
Class Design Principles: Single Responsibility Principle (SRP) - The Smart Home Example
Splitting by Inheritance
abstract class LocationWithShutters extends Location {
abstract List<Sutter> shutters();
abstract class LightedLocation extends Location {
...
} abstract List<Light> lights();
...
}
abstract class CompositeLocationWithShutters extends
CompositeLocation
abstract class { LightedCompositeLocation extends CompositeLocation {
...List<Light> lights() {
List<Light> lights = new ArrayList<Light>();
}
for (Location child : locations()) {
... lights.addAll(child.lights())}
return lights;
}
}
class LightedRoom extends Room {
List<Light> lights; What do you think
List<Light> lights() { return lights; } about the design?
}
class LightedFloor extends ...
class LightedHouse extends ...
...
House house = new LightedHouse();
...
35
Class Design Principles: Single Responsibility Principle (SRP) - The Smart Home Example
39
Class Design Principles: Single Responsibility Principle (SRP)
2.2.8 Takeaway
41
Class Design Principles
43
Class Design Principles: The Open-Closed Principle (OCP)
44
Class Design Principles: The Open-Closed Principle (OCP) - Extension and Modification
Most importantly: not changing existing code for the sake of
implementing extensions enables incremental compilation, testing,
debugging.
45
Class Design Principles: The Open-Closed Principle (OCP)
46
Class Design Principles: The Open-Closed Principle (OCP) - Abstraction is the Key
Object-oriented languages:
abstractions are encoded in abstract base classes, interfaces, generics
(type parameters), methods, …
the unbounded group of possible behaviors is represented by all the
possible derivative classes of an abstract class, the implementations
of an interface, the instances of a type parameter, all possible
arguments to a method, …
Functional languages:
abstractions are encoded in functions, generic data types/functions, …
the unbounded group of possible behaviors is represented by all the
possible calls of the functions, all possible type instantiations of
generic data types/functions, …
47
Class Design Principles: The Open-Closed Principle (OCP) - Abstraction is the Key
48
Class Design Principles: The Open-Closed Principle (OCP) - Abstraction is the Key
49
Class Design Principles: The Open-Closed Principle (OCP)
50
Class Design Principles: The Open-Closed Principle (OCP) - OCP by Example
51
Class Design Principles: The Open-Closed Principle (OCP) - OCP by Example
53
Class Design Principles: The Open-Closed Principle (OCP) - OCP by Example
Rigid designs are hard to change – every change causes many
changes to other parts of the system.
Our example design is rigid: Adding a new shape causes many existing
classes to be changed.
Fragile designs tend to break in many places when a single change is
made.
Our example design is fragile: Many switch/case (if/else) statements
that are both hard to find and hard to decipher.
54
Class Design Principles: The Open-Closed Principle (OCP) - OCP by Example
The design violates OCP with respect to extensions with new
kinds of shapes.
55
Class Design Principles: The Open-Closed Principle (OCP) - OCP by Example
An Alternative Design
public void drawAllShapes(List<Shape> shapes) {
for(Shape shape : shapes) {
shape.draw();
}
}
This solution
complies with OCP!
Extensibility:
Adding new shapes is easy! Just implement a new realization of Shape.
drawAllShapes only depends on Shape! We can reuse it efficiently.
56
Class Design Principles: The Open-Closed Principle (OCP) - OCP by Example
An Alternative Design
public void drawAllShapes(List<Shape> shapes) {
for(Shape shape : shapes) {
shape.draw();
}
}
This solution
complies with OCP!
57
Class Design Principles: The Open-Closed Principle (OCP) - OCP by Example
Problematic Changes
59
Class Design Principles: The Open-Closed Principle (OCP) - Abstractions May Support or Hinder Change
No matter how “open” a module is, there will always be some kind
of change that requires modifcation
Reason:
There is no model
that is natural to
all contexts.
60
Class Design Principles: The Open-Closed Principle (OCP) - Abstractions May Support or Hinder Change
61
Class Design Principles: The Open-Closed Principle (OCP) - Abstractions May Support or Hinder Change
62
Class Design Principles: The Open-Closed Principle (OCP) - Abstractions May Support or Hinder Change
“The show shall start with the pink pelicans and the African geese flying across
the stage. They are to land at one end of the arena and then walk towards a small
door on the side. At the same time, a killer whale should swim in circles and jump
just as the pelicans fly by. After the jump, the sea lion should swim past the
whale, jump out of the pool, and walk towards the center stage where the
announcer is waiting for him.”
63
Class Design Principles: The Open-Closed Principle (OCP) - Abstractions May Support or Hinder Change
Our current
programming
languages and tools
do not support well
modeling the world
based on co-existing
viewpoints.
64
Class Design Principles: The Open-Closed Principle (OCP) - Abstractions May Support or Hinder Change
65
Class Design Principles: The Open-Closed Principle (OCP)
Strategic Opening
Choose the kinds of changes against which to open your module.
Guess at the most likely kinds of changes.
Construct abstractions to protect from those changes.
66
Class Design Principles: The Open-Closed Principle (OCP) - Strategic and Agile Opening
Be Agile …
Guesses about likely kinds of changes that the application will suffer over
time will often be wrong.
67
Class Design Principles: The Open-Closed Principle (OCP)
2.3.6 Takeaway
No matter how “open” a module is, there will always be some
kind of change which requires modification.
68
Chapter Overview
69
Class Design Principles
70
Class Design Principles
71
Class Design Principles: Liskov Substitution Principle (LSP)
LSP provides us with design rules that govern this particular use
of inheritance and subtype polymorphism.
LSP:
gives us a way to characterize good inheritance hierarchies,
increases our awareness about traps that will cause us to create
hierarchies that do not conform to OCP.
72
Class Design Principles: Liskov Substitution Principle (LSP) - The Essence of LSP
73
Class Design Principles: Liskov Substitution Principle (LSP) - The Essence of LSP
...
}
75
Class Design Principles: Liskov Substitution Principle (LSP) - Introduction to LSP by Example
76
Class Design Principles: Liskov Substitution Principle (LSP) - Introduction to LSP by Example
A Broken Client
Java subtyping rules tell us that we can pass Square everywhere a
Rectangle is expected.
But, what happens if we pass a square to someClientMethod?
77
Class Design Principles: Liskov Substitution Principle (LSP) - Introduction to LSP by Example
78
Class Design Principles: Liskov Substitution Principle (LSP) - Introduction to LSP by Example
But, a Square does not comply with the expected behavior of a
Rectangle!
Changing the height/width of a Square, behaves differently from
changing the height/width of a Rectangle.
79
Class Design Principles: Liskov Substitution Principle (LSP) - Introduction to LSP by Example
80
Class Design Principles: Liskov Substitution Principle (LSP) - Introduction to LSP by Example
A LSP-Compliant Solution
Rectangle and Square are
siblings.
The interface Shape declares
common methods.
When clients use Shape as a representative for specific shapes they
cannot make any assumptions about the behavior of the methods.
Clients that want to change properties of shapes have to work with
the concrete classes and make specific assumptions about them.
81
Class Design Principles: Liskov Substitution Principle (LSP)
82
Class Design Principles: Liskov Substitution Principle (LSP)
Goal: Indicate that violations of LSP are realistic and sophisticated,
hence easy to run into them.
83
Class Design Principles: Liskov Substitution Principle (LSP) - More (Realistic) Examples
Derivates that document that certain methods inherited from the
super-class should not be called by clients.
…
84
Class Design Principles: Liskov Substitution Principle (LSP) - More (Realistic) Examples
Because Properties inherits from Hashtable, the put and putAll methods can be applied to a
Properties object. Their use is strongly discouraged as they allow the caller to insert entries
whose keys or values are not Strings. The setProperty method should be used instead. If the
store or save method is called on a "compromised" Properties object that contains a non-
String key or value, the call will fail.
…
85
Class Design Principles: Liskov Substitution Principle (LSP) - More (Realistic) Examples
86
Class Design Principles: Liskov Substitution Principle (LSP) - More (Realistic) Examples
87
Class Design Principles: Liskov Substitution Principle (LSP) - More (Realistic) Examples
A client adding elements to a set (fill method below) has no idea
whether the set is persistent and cannot know whether the elements to
fill must be of type PersistentObject.
Passing an arbitrary object will cause the cast in
PersistentSetAdapter to fail, breaking a method that worked fine
before PersistentAdpaterSet was introduced.
A method
public void fill(Set s) {
fill-the-set-with-some-objects
}
Somewhere else...
Set s = new PersistentSetAdapter(); // Problem!
fill (s);
88
Class Design Principles: Liskov Substitution Principle (LSP) - More (Realistic) Examples
Conclusion:
PersistentSetAdapter does not have a behavioral IS-A relationship to Set.
We must separate their hierarchies and make them siblings.
89
Class Design Principles: Liskov Substitution Principle (LSP)
90
Class Design Principles: Liskov Substitution Principle (LSP) - Mechanisms for Supporting LSP
We said:
A model viewed in isolation cannot be meaningfully validated
with respect to LSP!
Validity must be judged from the perspective of possible usages of the
model.
Hence, we need to anticipate assumptions that clients make about our
models – which is de facto impossible.
Most of the times we will only be able to view our model in isolation;
We do not know how it will be used and how it will be extended by
means of inheritance.
91
Class Design Principles: Liskov Substitution Principle (LSP) - Mechanisms for Supporting LSP
92
Class Design Principles: Liskov Substitution Principle (LSP) - Mechanisms for Supporting LSP - Explicit Contracts for Clients and Subclasses
Invariants
Properties that are always true for instances of the class.
May be broken temporarily during a method execution, but
otherwise hold.
93
Class Design Principles: Liskov Substitution Principle (LSP) - Mechanisms for Supporting LSP - Explicit Contracts for Clients and Subclasses
94
Class Design Principles: Liskov Substitution Principle (LSP) - Mechanisms for Supporting LSP - Explicit Contracts for Clients and Subclasses
95
Class Design Principles: Liskov Substitution Principle (LSP) - Mechanisms for Supporting LSP - Explicit Contracts for Clients and Subclasses
Behavioral Subtyping
96
Class Design Principles: Liskov Substitution Principle (LSP) - Mechanisms for Supporting LSP - Explicit Contracts for Clients and Subclasses
Behavioral Subtyping
97
Class Design Principles: Liskov Substitution Principle (LSP) - Mechanisms for Supporting LSP - Explicit Contracts for Clients and Subclasses
Type systems have some desirable properties that contracts do not
have (in general)
Static, compositional checking
That methods adhere to their declared types
That types of overridden methods are refined in a LSP-consistent
way
Such as covariance for return types, contravariance for argument
types
98
Class Design Principles: Liskov Substitution Principle (LSP) - Mechanisms for Supporting LSP - Explicit Contracts for Clients and Subclasses
This is not news, since already plain type checking is undecidable.
99
Class Design Principles: Liskov Substitution Principle (LSP) - Mechanisms for Supporting LSP - Explicit Contracts for Clients and Subclasses
100
Class Design Principles: Liskov Substitution Principle (LSP) - Mechanisms for Supporting LSP
101
Class Design Principles: Liskov Substitution Principle (LSP) - Mechanisms for Supporting LSP - Contracts in Documentation
102
Class Design Principles: Liskov Substitution Principle (LSP) - Mechanisms for Supporting LSP - Contracts in Documentation
It is reflexive:
For any reference value x, x.equals(x) must return true.
It is symmetric:
For any reference values x and y, x.equals(y) must return true if
and only if y.equals(x) returns true.
It is transitive:
For any reference values x, y, and z, if x.equals(y) returns true
and y.equals(z) returns true, then x.equals(z) must return true.
It is consistent:
For any reference values x and y, multiple invocations of
x.equals(y) consistently return true or consistently return false,
provided no information used in equals comparisons on the object is
modified.
For any non-null reference value x, x.equals(null) must return
false.
103
Class Design Principles: Liskov Substitution Principle (LSP) - Mechanisms for Supporting LSP - Contracts in Documentation
104
Class Design Principles: Liskov Substitution Principle (LSP) - Mechanisms for Supporting LSP - Contracts in Documentation
Read more in
http://java.sun.com/developer/Books/effectivejava/Chapter3.pdf
105
Class Design Principles: Liskov Substitution Principle (LSP) - Mechanisms for Supporting LSP - Contracts in Documentation
/**
* Case-insensitive string. Case of the original string is
* preserved by toString, but ignored in comparisons.
*/
public final class CaseInsensitiveString {
private String s;
public CaseInsensitiveString(String s) {
if (s == null) throw new NullPointerException();
this.s = s;
}
106
Class Design Principles: Liskov Substitution Principle (LSP) - Mechanisms for Supporting LSP - Contracts in Documentation
/**
* Case-insensitive string. Case of the original string is
* preserved by toString, but ignored in comparisons.
*/
public final class CaseInsensitiveString {
private String s;
public CaseInsensitiveString(String s) {
if (s == null) throw new NullPointerException();
this.s = s;
}
BROKEN:
public boolean equals(Object o) {
if (o instanceof CaseInsensitiveString)
Violates symmetry!
return s.equalsIgnoreCase(((CaseInsensitiveString)o).s);
if (o instanceof String)
return s.equalsIgnoreCase((String)o);
return false; One-way
} interoperability!
... // Remainder omitted
}
107
Class Design Principles: Liskov Substitution Principle (LSP) - Mechanisms for Supporting LSP - Contracts in Documentation
No one knows what list.contains(s) would return in the code below.
The result may vary from one Java implementation (of ArrayList) to
another.
...
CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
String s = "polish";
List list = new ArrayList();
list.add(cis);
...
return list.contains(s);
Once you have violated equals contract, you simply don’t know how
other objects will behave when confronted with your object.
108
Class Design Principles: Liskov Substitution Principle (LSP) - Mechanisms for Supporting LSP - Contracts in Documentation
This can cause the URL equals method to violate the equals contract,
and it has caused problems in practice.
(Unfortunately, this behavior cannot be changed due to compatibility
requirements.)
109
Class Design Principles: Liskov Substitution Principle (LSP) - Mechanisms for Supporting LSP - Contracts in Documentation
110
Class Design Principles: Liskov Substitution Principle (LSP) - Mechanisms for Supporting LSP - Contracts in Documentation
RFC 2119 defines keywords - may, should, must, etc. – which can be
used to express so-called „subclassing directives“.
/**
* Subclasses should override.
* Subclasses may call super
* New implementation should call addPage
*/
111
Class Design Principles: Liskov Substitution Principle (LSP) - Mechanisms for Supporting LSP - Contracts in Documentation
112
Class Design Principles: Liskov Substitution Principle (LSP) - Mechanisms for Supporting LSP - Contracts in Documentation
class Point2D {
...
/** Indicate whether two points are equal.
* Returns true iff the values of the respective
* x and y coordinates of this and ob are equal.
*/
114
Class Design Principles: Liskov Substitution Principle (LSP) - Mechanisms for Supporting LSP - Contracts in Documentation
115
Class Design Principles: Liskov Substitution Principle (LSP) - Mechanisms for Supporting LSP - Contracts in Documentation
One could also argue that Point3D should not be a subtype of Point2D
anyway.
116
Class Design Principles: Liskov Substitution Principle (LSP) - Mechanisms for Supporting LSP
117
Class Design Principles: Liskov Substitution Principle (LSP) - Mechanisms for Supporting LSP - Java Modeling Language (JML)
118
Class Design Principles: Liskov Substitution Principle (LSP)
119
Class Design Principles: Liskov Substitution Principle (LSP)
2.4.7 Takeaway
120
Chapter Overview
121
Class Design Principles
122
Class Design Principles
123
Class Design Principles: Interface Segregation Principle (ISP)
When clients are forced to depend on methods they do not use, they
become subject to changes to these methods, which other clients
force upon the class.
124
Class Design Principles: Interface Segregation Principle (ISP)
125
Class Design Principles: Interface Segregation Principle (ISP) - Introduction to ISP by Example
Having the option of calling changePermissions() does not make sense
when implementing UserClient.
The programmer must avoid calling it by convention instead of by design.
126
Class Design Principles: Interface Segregation Principle (ISP) - Introduction to ISP by Example
A Polluted Interface
127
Class Design Principles: Interface Segregation Principle (ISP) - Introduction to ISP by Example
A ISP-Compliant Solution
128
Class Design Principles: Interface Segregation Principle (ISP)
Try to group possible clients of a class and have an interface for each
group.
129
Class Design Principles: Interface Segregation Principle (ISP)
2.5.4 Takeaway
130
Chapter Overview
131
Class Design Principles
132
Class Design Principles
133
Class Design Principles: Dependency Inversion Principle (DIP)
High-level policy:
The abstraction that underlies the application;
the truth that does not vary when details are changed;
the system inside the system;
the metaphor.
134
Class Design Principles: Dependency Inversion Principle (DIP) - The Rationale of DIP
It is the high-level modules that should influence the low-level details
135
Class Design Principles: Dependency Inversion Principle (DIP)
Button
Is capable of “sensing” whether it has been activated/deactivated by
the user.
Once a change is detected, it turns the Lamp on respectively off.
136
Class Design Principles: Dependency Inversion Principle (DIP) - Introduction to DIP by Example
137
Class Design Principles: Dependency Inversion Principle (DIP) - Introduction to DIP by Example
138
Class Design Principles: Dependency Inversion Principle (DIP) - Introduction to DIP by Example
A DIP-Compliant Solution
139
Class Design Principles: Dependency Inversion Principle (DIP) - Introduction to DIP by Example
A Quick Quiz
140
Class Design Principles: Dependency Inversion Principle (DIP) - Introduction to DIP by Example
141
Class Design Principles: Dependency Inversion Principle (DIP)
Grady Booch
„…all well-structured object-oriented architectures have clearly
defined layers, with each layer providing some coherent set of
services through a well-defined and controlled interface…“
142
Class Design Principles: Dependency Inversion Principle (DIP) - Layers and Dependencies
143
Class Design Principles: Dependency Inversion Principle (DIP) - Layers and Dependencies
145
Class Design Principles: Dependency Inversion Principle (DIP) - Naive Heuristic for Ensuring DIP
The heuristic seems naive for concrete stable classes, e.g., String in Java.
But, concrete application classes are generally volatile, should not depend
on them. Their volatility can be isolated:
by keeping them behind abstract interfaces
that are owned by clients.
146
Class Design Principles: Dependency Inversion Principle (DIP)
2.6.5 Takeaway
Rationale behind DIP is arguable. For example, what if existing 3rd party (low-level) libraries are
used?
One can argue that DIP enables an adapter layer which stops propagation of changes
147