Presented at DevSum (2018-05-31)
The SOLID principles are often presented as being core to good code design practice. Each of S, O, L, I and D do not, however, necessarily mean what programmers expect they mean or are taught. By understanding this range of beliefs we can learn more about practices for objects, components and interfaces than just S, O, L, I and D.
This talk reviews the SOLID principles and reveals contradictions and different interpretations. It is through paradoxes and surprises we often gain insights. We will leave SOLID somewhat more fluid, but having learnt from them more than expected.
6. In object-oriented programming, the single
responsibility principle states that every object
should have a single responsibility, and that
responsibility should be entirely encapsulated by
the class. All its services should be narrowly
aligned with that responsibility.
http://en.wikipedia.org/wiki/Single_responsibility_principle
7. The single responsibility principle is a computer
programming principle that states that every
module or class should have responsibility over a
single part of the functionality provided by the
software, and that responsibility should be entirely
encapsulated by the class. All its services should be
narrowly aligned with that responsibility.
http://en.wikipedia.org/wiki/Single_responsibility_principle
8. The single responsibility principle is a computer
programming principle that states that every
module or class should have responsibility over a
single part of the functionality provided by the
software, and that responsibility should be entirely
encapsulated by the class. All its services should be
narrowly aligned with that responsibility.
http://en.wikipedia.org/wiki/Single_responsibility_principle
9. The term was introduced by Robert C. Martin [...].
Martin described it as being based on the principle
of cohesion, as described by Tom DeMarco in his
book Structured Analysis and Systems Specification.
http://en.wikipedia.org/wiki/Single_responsibility_principle
10.
11. Cohesion is a measure of the strength of
association of the elements inside a module.
A highly cohesive module is a collection of
statements and data items that should be treated
as a whole because they are so closely related.
Any attempt to divide them up would only result
in increased coupling and decreased readability.
15. This is the Unix philosophy:
Write programs that do one
thing and do it well.Write
programs to work together.
Doug McIlroy
16.
17. In McIlroy's summary, the hard
part is his second sentence:
Write programs to work
together.
John D Cook
18.
19. In the long run every
program becomes rococo
— then rubble.
Alan Perlis
20. We refer to a sound line of reasoning,
for example, as coherent. The thoughts
fit, they go together, they relate to each
other.
GlennVanderburg
http://vanderburg.org/blog/2011/01/31/cohesion.html
21. This is exactly the characteristic of a
class that makes it coherent: the pieces
all seem to be related, they seem to
belong together, and it would feel
somewhat unnatural to pull them apart.
Such a class exhibits cohesion.
GlennVanderburg
http://vanderburg.org/blog/2011/01/31/cohesion.html
26. One of the most foundational
principles of good design is:
Gather together those things
that change for the same reason,
and separate those things that
change for different reasons.
This principle is often known as
the single responsibility principle,
or SRP. In short, it says that a
subsystem, module, class, or even
a function, should not have more
than one reason to change.
28. Interface inheritance (subtyping) is used whenever
one can imagine that client code should depend on
less functionality than the full interface.
Services are often partitioned into several unrelated
interfaces when it is possible to partition the clients
into different roles.
"General Design Principles"
CORBAservices
30. This is exactly the characteristic of a
class that makes it coherent: the pieces
all seem to be related, they seem to
belong together, and it would feel
somewhat unnatural to pull them apart.
Such a class exhibits cohesion.
GlennVanderburg
http://vanderburg.org/blog/2011/01/31/cohesion.html
31. This is exactly the characteristic of an
interface that makes it coherent: the
pieces all seem to be related, they seem
to belong together, and it would feel
somewhat unnatural to pull them apart.
Such an interface exhibits cohesion.
37. Concept Hierarchies
The construction principle involved is best
called abstraction; we concentrate on features
common to many phenomena, and we abstract
away features too far removed from the
conceptual level at which we are working.
Ole-Johan Dahl and C A R Hoare
"Hierarchical Program Structures"
38. A type hierarchy is composed of subtypes and
supertypes. The intuitive idea of a subtype is
one whose objects provide all the behavior of
objects of another type (the supertype) plus
something extra.
Barbara Liskov
"Data Abstraction and Hierarchy"
39. 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
"Data Abstraction and Hierarchy"
46. public class RecentlyUsedList
{
private IList<string> items = new List<string>();
public int Count => items.Count;
public string this[int index] => items[index];
public void Add(string newItem)
{
if(newItem == null)
throw new ArgumentNullException();
items.Remove(newItem);
items.Insert(0, newItem);
}
...
}
47. public class RecentlyUsedList : List<string>
{
public override void Add(string newItem)
{
if(newItem == null)
throw new ArgumentNullException();
items.Remove(newItem);
items.Insert(0, newItem);
}
...
}
48. namespace List_spec
{
...
[TestFixture]
public class Addition
{
private List<string> list;
[Setup]
public void List_is_initially_empty()
{
list = ...
}
...
[Test]
public void Addition_of_non_null_item_is_appended() ...
[Test]
public void Addition_of_null_is_permitted() ...
[Test]
public void Addition_of_duplicate_item_is_appended() ...
...
}
...
}
49. namespace List_spec
{
...
[TestFixture]
public class Addition
{
private List<string> list;
[Setup]
public void List_is_initially_empty()
{
list = new List<string>();
}
...
[Test]
public void Addition_of_non_null_item_is_appended() ...
[Test]
public void Addition_of_null_is_permitted() ...
[Test]
public void Addition_of_duplicate_item_is_appended() ...
...
}
...
}
50. namespace List_spec
{
...
[TestFixture]
public class Addition
{
private List<string> list;
[Setup]
public void List_is_initially_empty()
{
list = new RecentlyUsedList();
}
...
[Test]
public void Addition_of_non_null_item_is_appended() ...
[Test]
public void Addition_of_null_is_permitted() ...
[Test]
public void Addition_of_duplicate_item_is_appended() ...
...
}
...
}
51. 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
"Data Abstraction and Hierarchy"
52. 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
"Data Abstraction and Hierarchy"
53. 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
"Data Abstraction and Hierarchy"
54. 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
"Data Abstraction and Hierarchy"
56. Bertrand Meyer gave us guidance as long
ago as 1988 when he coined the now famous
open-closed principle. To paraphrase him:
Software entites (classes, modules,
functions, etc.) should be open for
extension, but closed for modification.
"The Open-Closed Principle", Robert C Martin
C++ Report, January 1996
57. Open for
extension
Closed for
extension
Closed for
modification
Open for
modification
Extend existing code
without changing it,
e.g., using a framework
Extend or change existing
code, e.g., open-source
and non-published code
Non-extensible and
unchangeable code,
e.g., composable code
or legacy code
Non-extensible code that
can be maintained, e.g.,
composable code in a
maintained library
58.
59. The principle stated that a good module structure
should be both open and closed:
▪ Closed, because clients need the module's
services to proceed with their own development,
and once they have settled on a version of the
module should not be affected by the
introduction of new services they do not need.
▪ Open, because there is no guarantee that we
will include right from the start every service
potentially useful to some client.
60. [...] A good module structure should
be [...] closed [...] because clients
need the module's services to
proceed with their own development,
and once they have settled on a
version of the module should not be
affected by the introduction of new
services they do not need.
61.
62. There is no problem changing a
method name if you have access to
all the code that calls that method.
Even if the method is public, as long
as you can reach and change all the
callers, you can rename the method.
63. There is a problem only if the
interface is being used by code that
you cannot find and change. When
this happens, I say that the interface
becomes a published interface (a
step beyond a public interface).
64. The distinction between published
and public is actually more
important than that between public
and private.
Martin Fowler
https://martinfowler.com/bliki/PublishedInterface.html
65. With a non-published interface you
can change it and update the calling
code since it is all within a single
code base.
But anything published so you can't
reach the calling code needs more
complicated treatment.
Martin Fowler
https://martinfowler.com/bliki/PublishedInterface.html
66. [...] A good module structure should
be [...] open [...] because there is no
guarantee that we will include right
from the start every service potentially
useful to some client.
67.
68. Speculative Generality
Brian Foote suggested this name for a
smell to which we are very sensitive.
You get it when people say, "Oh, I think
we need the ability to do this kind of
thing someday" and thus want all sorts
of hooks and special cases to handle
things that aren't required.
70. A myth in the object-oriented design
community goes something like this:
If you use object-oriented technology,
you can take any class someone else
wrote, and, by using it as a base class,
refine it to do a similar task.
Robert B Murray
C++ Strategies andTactics
71.
72. Design and implement for
inheritance or else prohibit it
By now, it should be apparent that
designing a class for inheritance places
substantial limitations on the class.
75. I've heard it said that the OCP is
wrong, unworkable, impractical,
and not for real programmers
with real work to do. The rise of
plugin architectures makes it
plain that these views are utter
nonsense. On the contrary, a
strong plugin architecture is likely
to be the most important aspect
of future software systems.
https://8thlight.com/blog/uncle-bob/2014/05/12/TheOpenClosedPrinciple.html
79. public abstract class Shape
{
...
}
public class Square : Shape
{
...
public void DrawSquare() ...
}
public class Circle : Shape
{
...
public void DrawCircle() ...
}
80. public abstract class Shape ...
public class Square : Shape ...
public class Circle : Shape ...
static void DrawAllShapes(Shape[] list)
{
foreach (Shape s in list)
if (s is Square)
(s as Square).DrawSquare();
else if (s is Circle)
(s as Circle).DrawCircle();
}
81. public abstract class Shape ...
public class Square : Shape ...
public class Circle : Shape ...
static void DrawAllShapes(Shape[] list)
{
foreach (Shape s in list)
if (s is Square)
(s as Square).DrawSquare();
else if (s is Circle)
(s as Circle).DrawCircle();
}
82. public abstract class Shape
{
...
public abstract void Draw();
}
public class Square : Shape
{
...
public override void Draw() ...
}
public class Circle : Shape
{
...
public override void Draw() ...
}
83. public abstract class Shape ...
public class Square : Shape ...
public class Circle : Shape ...
static void DrawAllShapes(Shape[] list)
{
foreach (Shape s in list)
s.Draw();
}
84. public abstract class Shape ...
public class Square : Shape ...
public class Circle : Shape ...
static void DrawAllShapes(Shape[] list)
{
foreach (Shape s in list)
s.Draw();
}
85. public abstract class Shape ...
public class Square : Shape ...
public class Circle : Shape ...
static void DrawAllShapes(Shape[] list)
{
foreach (Shape s in list)
s.Draw();
}
86. Bertrand Meyer gave us guidance as long
ago as 1988 when he coined the now famous
open-closed principle. To paraphrase him:
Software entites (classes, modules,
functions, etc.) should be open for
extension, but closed for modification.
"The Open-Closed Principle", Robert C Martin
C++ Report, January 1996
87. Bertrand Meyer gave us guidance as long
ago as 1988 when he coined the now famous
open-closed principle. To paraphrase him:
Software entites (classes, modules,
functions, etc.) should be open for
extension, but closed for modification.
"The Open-Closed Principle", Robert C Martin
C++ Report, January 1996
88.
89. This double requirement looks
like a dilemma, and classical
module structures offer no clue.
But inheritance solves it.
A class is closed, since it may
be compiled, stored in a library,
baselined, and used by client
classes. But it is also open, since
any new class may use it as
parent, adding new features.
90. public abstract class Shape ...
public class Square : Shape ...
public class Circle : Shape ...
static void DrawAllShapes(Shape[] list)
{
foreach (Shape s in list)
if (s is Square)
(s as Square).DrawSquare();
else if (s is Circle)
(s as Circle).DrawCircle();
}
91. public abstract class Shape ...
public class Square : Shape ...
public class Circle : Shape ...
static void DrawAllShapes(Shape[] list)
{
foreach (Shape s in list)
if (s is Square)
(s as Square).DrawSquare();
else if (s is Circle)
(s as Circle).DrawCircle();
}
92. public abstract class Shape ...
public class Square : Shape ...
public class Circle : Shape ...
static void DrawAllShapes(Shape[] list)
{
foreach (Shape s in list)
s.Draw();
}
93. public abstract class Shape ...
public class Square : Shape ...
public class Circle : Shape ...
static void DrawAllShapes(Shape[] list)
{
foreach (Shape s in list)
s.Draw();
}
94. public sealed class Shape
{
public enum Type { Square, Circle }
...
public void Draw() ...
}
static void DrawAllShapes(Shape[] list)
{
foreach (Shape s in list)
s.Draw();
}
95. public sealed class Shape
{
public enum Type { Square, Circle }
...
public void Draw() ...
}
static void DrawAllShapes(Shape[] list)
{
foreach (Shape s in list)
s.Draw();
}
96.
97. Don't publish interfaces prematurely.
Modify your code ownership policies
to smooth refactoring.
99. The principle states:
A. High-level modules should not depend on
low-level modules. Both should depend on
abstractions.
B. Abstractions should not depend upon details.
Details should depend upon abstractions.
https://en.wikipedia.org/wiki/Dependency_inversion_principle
100.
101.
102. Program to an interface,
not an implementation.
Erich Gamma, Richard Helm, Ralph Johnson & John Vlissides
106. One of the most foundational
principles of good design is:
Gather together those things
that change for the same reason,
and separate those things that
change for different reasons.
This principle is often known as
the single responsibility principle,
or SRP. In short, it says that a
subsystem, module, class, or even
a function, should not have more
than one reason to change.
107. Write components that do one thing
and do it well.
Write components to work together.
Program to an interface, not an
implementation.
Don't publish interfaces prematurely.
Modify your code ownership policies
to smooth refactoring.