Single Responsibility Principle
"There should never be more than one reason for a class to change." — Robert Martin, SRP paper linked from The Principles of OOD
My translation: A class should concentrate on doing one thing
The SRP says a class should focus on doing one thing, or have one responsibility.
This doesn’t mean it should only have one method, but instead all the
methods should relate to a single purpose (i.e. should be cohesive).
For example, an
Invoice
class might have the
responsibility of calculating various amounts based on it’s data. In
that case it probably shouldn’t know about how to retrieve this data
from a database, or how to format an invoice for print or display.
A class that adheres to the SRP should be easier to change than those
with multiple responsibilities. If we have calculation logic and
database logic and display logic all mixed up within one class it can be
difficult to change one part without breaking others. Mixing
responsibilities also makes the class harder to understand, harder to
test, and increases the risk of duplicating logic in other parts of the
design (decreases cohesion, functionality has no clear place to live).
Violations of the SRP are pretty easy to notice: the class seems to
be doing too much, is too big and too complicated. The easiest way to
fix this is to split the class.
The main trick in following the SRP is deciding how to define the
single responsibility. There may be many ways to dissect a feature into
responsibilities, but the ideal way is to use responsibilities that are
likely to change independently, hence the official description: "A class
should have one, and only one, reason to change".
Open Closed Principle
"Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification." — Robert Martin paraphrasing Bertrand Meyer, OCP paper linked from The Principles of OOD
My translation: Change a class’ behaviour using inheritance and composition
Bob Martin’s initial paper on the OCP linked from The Principles of OOD
attributes the idea to Bertrand Meyer, who wrote that classes should be
“open for extension, but closed for modification”[2]. The idea is that
we can use OO techniques like inheritance and composition to change (or extend) the behaviour of a class, without modifying the class itself.
Say we have an
OrderValidation
class with one big Validate(Order order)
method that contains all rules required to validate an order. If the rules change, we need to change or OrderValidation
class, so we are violating the OCP. If the OrderValidation
contained a collection of IValidationRule
objects that contained the rules, then we could write Validate(Order order)
to iterate through those to validate the order. Now if the rules change then we can just create a new IValidationRule
and add it to an OrderValidation
instance at run time (rather than to the class definition itself).
Following the OCP should make behaviour easier to change, and also
help us avoid breaking existing behaviour while making changes. The OCP
also gets us to think about the likely areas of change in a class, which
helps us choose the right abstractions required for our design.
If you find you need to modify a similar area of code all the time
(for example, validation rules) then it’s probably time to apply the OCP
and abstract away the changing part of the code. Another sign of a
potential OCP violation is switching on a type — if another type is
created then we’ll have to alter the switch statement. A healthy dose of polymorphism is generally the best treatment. :) I generally think of the OCP as an advertisement for the Template Method and Strategy design patterns.
Liskov Substitution Principle
"Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it." — Robert Martin, LSP paper linked from The Principles of OOD
My translation: Subclasses should behave nicely when used in place of their base class
The LSP sounds deceptively straightforward — we should be able to
substitute an instance of a subclass for its parent class and everything
should continue to work. Easy right? Well, actually, no it’s not, which
is probably why we are often advised to favour composition over inheritance.
Ensuring a base class works in any situation the parent does is really
hard work, and whenever you use inheritance its a good idea to keep the
LSP firmly in mind.
The canonical example of an LSP violation (in fact, the one used in the Hanselminutes episode on SOLID mentioned earlier) is the
Square
IS-A Rectangle
relationship. Mathematically a square is a special case of a rectangle
with all sides of equal length, but this breaks the LSP when modelled in
code. What should SetWidth(int width)
do when called on a Square
? Should it set the height as well? What if you have a reference to it via its base class, Rectangle
?
If you have code that expects one behaviour but gets another depending
on which subtype it has, you can wind up with some very hard to find
bugs.
LSP violations can be easy to miss until you actually hit the condition where your inheritance hierarchy breaks down (I mean, a square IS-A rectangle,
right?). The best way to reduce violations is to keep very aware of the
LSP whenever using inheritance, including considering avoiding the
problem using composition where appropriate.
Interface Segregation Principle
"Clients should not be forced to depend upon interfaces that they do not use." — Robert Martin, ISP paper linked from The Principles of OOD
My translation: Keep interfaces small and cohesive
The ISP is about keeping interfaces (both
interface
, and abstract class
types of interfaces*)
small and limited only to a very specific need (a single responsibility
even :)). If you have a fat interface then you are imposing a huge
implementation burden on anyone that wants to adhere to that contract.
Worse still is that there is a tendency for class to only provide valid
implementations for a small portion of a fat interface, which greatly
diminishes the advantages of having an interface at all (note that these
partial implementations violate the LSP, as we can no longer treat all
subclasses of the interface equally).
* While I originally wrote this in terms of
interface code constructs, I’ve always thought more about the interface
in ISP as the public interface for interacting with an object, even if
this is just the public methods of a class. This becomes more relevant
in dynamic languages where interfaces are implied rather than explicit.
In the dynamic languages case, ISP becomes more a statement of SRP:
keeping a small interface to expose a single responsibility. [Added
2011-02-18]
The first time I recognised a violation of the ISP was writing a minimal implementation of an ASP.NET
RoleProvider
, which required an implementation of the following methods:public class MyRoleProvider : RoleProvider { public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config) { ... } public override void AddUsersToRoles(string[] usernames, string[] roleNames) { ... } public override string ApplicationName { get { ... } set { ... } } public override void CreateRole(string roleName) { ... } public override bool DeleteRole(string roleName, bool throwOnPopulatedRole) { ... } public override string[] FindUsersInRole(string roleName, string usernameToMatch) { ... } public override string[] GetAllRoles() { ... } public override string[] GetRolesForUser(string username) { ... } public override string[] GetUsersInRole(string roleName) { ... } public override bool IsUserInRole(string username, string roleName) { ... } public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames) { ... } public override bool RoleExists(string roleName) { ... } }
In my case I just wanted to use ASP.NET’s built in facility for securing pages by role in the
web.config
, which means I needed to implement GetRolesForUser(...)
and Initialize(...)
. Can you guess what the other implementations were? That’s right, throw new NotImplementedException();
. This is very bad — if we have a RoleProvider
instance we have no idea what sub-features it will support. On top of
that we also have a lot of useless noise in our class. (If you like the RoleProvider
, you might also enjoy the MembershipProvider
.)
The way to fix violations like this is to break down interfaces along the lines of responsibilities and apply the SRP. For the
RoleProvider
case, even if we just split it into IRolesForUserLookup
and IRoleManagement
(yuk), that would let us only implement what we need. If we need all
the features then we can implement both interfaces, but we should not be
forcing clients to fake or throw in implementations that are
meaningless to them.Dependency Inversion Principle
"A. High level modules should not depend upon low level modules. Both should depend upon abstractions.
B. Abstractions should not depend upon details. Details should depend upon abstractions." — Robert Martin, DIP paper linked from The Principles of OOD
My translation: Use lots of interfaces and abstractions
The DIP says that if a class has dependencies on other classes, it
should rely on the dependencies’ interfaces rather than their concrete
types. The idea is that we isolate our class behind a boundary formed by
the abstractions it depends upon. If all the details behind those
abstractions change then our class is still safe. This helps keep
coupling low and makes our design easier to change.
At its simplest, this can just be the difference between referencing an
EmployeeFinder
class or an IEmployeeFinder
interface. The concrete EmployeeFinder
class can access a database or a file, but the client class only cares that it meets the IEmployeeFinder
contract. Better yet, our client class doesn’t have to be tied in any way to the EmployeeFinder
class. It could instead use SqlEmployeeFinder
, XmlEmployeeFinder
, WebServiceEmployeeFinder
or MockEmployeeFinder
.
Where the DIP starts to become really useful and a bit more profound is in a related concept, Dependency Injection.
Dependency Injection is about getting other code to insert the actual
dependency instances into our class, so we don’t even have the client
class
new
ing up any of the concrete instances. This
completely isolates our class and makes change and reuse much easier.
(I’ve covered some introductory stuff in a previous ramble on dependency injection).
The other side of the DIP relates to dependencies between high and
low level modules in layered applications. For example, a class
accessing the database should not depend on a UI form used to display
that data. Instead the UI should rely on an abstraction (or
abstractions) over the database access class. Traditional application
layers (data, logic, ui) seem largely replaced by MVC, onions and hexagons these days, so I tend to think about the DIP entirely from the point of view of abstracting dependencies.
SOLID principles as a whole
You can probably see that the SOLID principles overlap a lot. For
example, the SRP provides a good way of splitting interfaces to follow
the ISP. The ISP helps implementers conform to the LSP by making
implementations small and cohesive. You may also notice that some of the
principles contradict, or at least pull in opposing directions, such as
the OCP requiring inheritance while the LSP tends to discourage it[3].
This interplay between the principles can provide a really useful guide
while developing your design. I’m believe there are no perfect designs,
just trade offs, and the SOLID principles can help you evaluate these
and achieve a good balance. The fact that there is some measure of
conflict between them also makes it obvious that none of the principles
should be applied rigidly or dogmatically. Do you really need a huge
interface explosion due while adhering to the OCP and DIP? Maybe, maybe
not. But considering your design options in light of the SOLID
principles can help you decide.
A lot of SOLID principles seem to fall out fairly naturally if you
practice TDD (or BDD). For example, writing an effective unit test for a
class is much easier if you follow the DIP and isolate your class from
its dependencies. If you are writing the tests first to drive your
design, then your class will naturally tend to use the DIP. If you are
retro-fitting tests, then you’ll likely encounter more difficulties
testing, and may end up with interaction-style tests, re-writing the
class to use the DIP, or worse, throwing it in the too hard-to-test
basket.
This is what people mean when they say that TDD and "testability" is
not about testing, it is about design. Scott Bellware recently published
a good post on design, SOLID and testability that goes into this in more detail.