An introduction to the SOLID principles of OO design

I would be very hard pressed to go passed Robert "Uncle Bob" Martin’s SOLID principles as the single most useful tool for helping me with OO design. Some people seem to have a natural talent for OO design and thinking, and have an innate understanding of these principles without requiring the SOLID formalism. I, on the other hand, find them indispensable.

Unfortunately the names used in the somewhat-cryptic "acronym of acronyms"[1] don’t exactly lend themselves to being easily learned (Bob Martin and Scott discussed how this came about on a Hanselminutes episode on SOLID, which is well worth a listen). I thought I’d post my interpretation of the principles as a refresher for myself, and as an introduction for anyone trying the learn the terminology.

I should point out I am far from a guru on this stuff, so if there’s any misinformation in here please point it out in a comment or email, so I can correct the post and learn me some OO :).

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 rules 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 parent 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 subclass 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 parent 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 newing 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 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.

Conclusion

This has been a quick introduction to the SOLID principles. Hopefully it will make it easier for people to jump into the gory details of all the principles. Even if you have an innate grasp on these principles, I think it is worth going to the trouble to learn them, if only to help get a common language with which to discuss these principles.

Recommended reading and listening

References

[1] Yammer exchange with Xerxes
[2] Bertrand Meyer, Object-Oriented Software Construction
[3] Gene Shadrin, Three Sources of a Solid Object-Oriented Design, SYS-CON Media Inc.

Comments