This is an article was first published in the antique Barbarian Meets Coding website
When you start learning any object-oriented programming language, you usually go through the very basic OOP concepts: abstraction, encapsulation, messaging, modularity, inheritance and polymorphism. You learn these concepts, these mechanisms for code reuse and maintainability, how to use them, the syntax but they don’t usually teach you how to leverage them. In other words, you don’t really learn how to use them well. So in this post, I’ll try to remedy that by giving an introduction to the principles of object oriented design as devised by Robert C. Martin (a.k.a. Uncle Bob).
I’ve been thinking it for a while, and I believe it would be a great idea to start a series of posts about programming best-practices, do’s and dont’s, conventions, and design patterns, both to create more awareness and share this wonderful knowledge with other developers that might not have heard of them yet, and also so I have a nice quick reference that I can come back to at any given time. Let’s jump into it!
The Principles of Object Oriented Design. Classes
The principles of object oriented design are condensed under the famous and catchy acronym of acronyms SOLID which stands for:
- S – The Single Responsibility Principle (SRP)
- O- The Open Closed Principle (OCP)
- L – The Liskov Substitution Principle (LSP)
- I – The Interface Segretation Principle (ISP)
- D – The Dependency Inversion Principle (DIP)
These principles represent a set of rules that allows us to improve the way we design and set up the dependencies between our classes and, in term, allow us to create more flexible, reusable and robust code. In the next following paragraphs I’ll be explaining shortly what they are all about, and how they can improve the code we write.
The Single Responsibility Principle
“A class should have one, and only one, reason to change or,
there should never be more than one reason for a class to change.”
This principle is based upon the fact that whenever a class handles more than one responsibility then there will be more than one reason for it to change. The consequences are pretty clear, if we need to change a given class for a number of different reasons, then this class is not going to be robust nor easy to maintain. In this case, there is what we call a coupling of responsibilities that will lead to a number of problems: modifying one of them, may have a negative effect on the others, it will imply the need to recompile code that wouldn’t be necessary if the responsibilities were decoupled, and it will allow the client of the class to access other responsibilities which it might not care for, are just some examples.
Applying this principle, may be hard at first, as we usually tend to group functionalities within our classes in the abstraction phase of our development cycle, but it is a great guideline nonetheless when we refactor. Use this principle, and you’ll end up with smaller, well-defined, robust and maintainable classes.
An example of a SRP violation could be a given Rectangle class which has a Draw method and a CalculateArea method. In this context, the class is handling two responsibilities, it has two reasons to change and thus is violating SRP. A way to decouple these responsibilities would be to extract separate interfaces, and make the clients depend upon these interfaces.
The Open Closed Principle
“You should be able to extend a class behavior, without modifying
A class should be open for extension but closed to
If you have been working in any brand of software development for any reasonable amount of time you’ll have notice that one thing that characterises most projects is that your software, for one reason or another, is going to change during its life cycle. Thus, it is pretty important to design your software so it will be flexible (i.e. receives changes well) but also so it is robust (it doesn’t break, a single change doesn’t result in an endless chain of changes). The Open Closed Principle does just that, it leads our efforts when tackling this particular problem, saying that, whenever our application needs changes, we should never modify old code that already works and is tested, but extend it with brand new functionalities and code.
The Open Closed Principle is comprised by two main concepts or corollaries:
- A class should be open for extension, which basically means that you should be able to extend the behavior or functionality of this given class.
- A class should be closed for modification, which means that you should never modify the existing code of a class
But how do we obtain classes that adhere to the OCP? How do we achieve this? The answer is through abstraction. Using interfaces or abstract classes we can define a fixed contract to follow whilst being able to extend the behavior with as many implementations of those interfaces as we want or need. It is to be noted though, that designing a class that is completely closed to any possible change, it is quite an impossible task. Most of the times, you’ll have to decide strategically which kind of changes your class will be prepared to handle (strategic closure).
As Robert C. Martin states, there are a great number of heuristics and common practices we use daily that derive directly from this principle. For instance, never use global variables, as no module that depends on a given global variable can be closed in relation to other module that might write into it. Also, making all members of a class private (alas encapsulation) prevents others classes from depending on these members and thus closes them.
You can get some extended information within this OCP article.
As a last note of interest, although I have been mainly talking about classes, you should know that
you can apply the OCP not only to classes, but to any software entity that you can think of, methods, modules, anything. Use this principle, and you’ll improve the maintainability of your code drastically.
The Liskov Substitution Principle
“Derived classes must be substitutable for their base classes or,
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.”
As mentioned in the previous section, we make use of abstractions (and polymorphism) to let our classes adhere to the Open-Closed Principle. In order to make a proper use of inheritance, so our derived classes will still subscribe to OCP, we guide ourselves by the Liskov Substitution Principle.
The whole point here is that a derived class should work as expected i.e. should behave (at a minimum common denominator) as portrayed by the base class, so that, classes or methods that have a reference to the base class, will be able to use instances of the derived classes without being aware of it(thus avoiding hard-coded dependencies).
If you listen to Hanselminutes podcast featuring Robert C. Martin, Robert puts the classical example of the Rectangle base class and the Square class that derives from it. So you have a Rectangle class in your application, and you want to use a new Square class, so you think… well a Square “is-a” Rectangle isn’t it? (it just has the same width and height) So you make it derive from the Rectangle class and wire it up so when you set the width, the height will be set as well and vice versa. Bang! Problem is that another developer that is working with Rectangles, doesn’t need to know what Rectangles really are at runtime, but still expects them to behave according to the base class definition. In this case, Square doesn’t conform to LSP and may end up causing problems based on unfulfilled assumptions. The point here is that, while it is a valid logical premise to relate a square with a rectangle, it might not be as useful from a code perspective. This example is also known as the circle-ellipse problem.
This was a pretty subtle violation of LSP. However, in practice, any time you use the common switch statement to check the type of an object at runtime in order to do this or that, you are violating LSP and OCP (new derived types will force you to modify that switch block).
The Interface Segregation Principle
“Make fine grained interfaces that are client specific or, Clients
should not be forced to depend upon interfaces that they do not
This principle may be one of the least importance due to its very focused field of application: fat interfaces. The whole point is that, when we have a class that has a fat interface (an interface with
a lot of methods) and a series of clients that depend upon this interface, we should refactor our solution so that, any given client only depends on a new interface that comprises only those methods of the original interface that the client needs to use. This is, the solution is to group the methods of the fat interface, into smaller and more cohesive interfaces.
The problem of using fat non-cohesive interfaces is that, they create a coupling between its clients, i.e. a given client may force changes on the interface that will impact another client even if they use non-related methods. Again, the solution to this problem is to extract smaller interfaces for each client to depend upon. This way, changes will be more contained and decoupled, as clients will only depend on their respective interfaces.
The Dependency Inversion Principle
“Depend on abstractions, not on concretions or, A. High level
modules should not depend upon low level modules. Both should
depend upon abstractions. Abstractions should not depend upon
details B. Abstraction should not depend upon details. Details
should depend upon abstractions.”
When I introduced the OCP before in this article, I said that the way to implement it was to make use of abstractions and polymorphism. I also introduced LSP in order to guide the implementation of inheritance so our derived classes won’t break OCP. So, if you use both OCP and LSP strictly, you will notice how a new pattern or structure emerges from it that can be generalized into what is known as the Dependency Inversion Principle.
This is one of the most useful principles, as it allow us to design software that is flexible (easy to change or extend), robust (reacts well to changes i.e. doesn’t break everywhere) and reusable (the parts of the system are very decoupled and we can extract them and use them in other projects), and whose main aim is to address bad design. The cause of bad designed software – software that is rigid, fragile and inmobile (opposite to flexible, robust and reusable in this case) – is the heavy hard-coded dependencies between its modules. These dependencies can in turn force the need of a cascade of changes when we want to introduce a tiny little change in the system (rigidity), or can result in a chain of unexpected errors (fragility), and of course, make impossible to reuse code in other applications because everything is so entwined that we might as well bring together the whole system.
The DIP addresses this problem saying no to hard-coded and top-down dependencies. The high- level modules should not depend upon the low-level modules, everything has to depend upon abstractions (thereby we get and “inverted” dependency). This way, the high level modules don’t know exactly what they depend upon, they just know they are using something that must adhere to a given interface, and thus, everything that follows that interface contract can be plugged in (or plugged out). If you extend the principle to the whole system you end up with a set of highly decoupled modules, that are completely isolated from changes in other modules and that can be easily reused. You end up with a well defined set of interfaces or abstractions that define the policy of your system, and a set of concrete implementations that are connected via these abstractions.
Where Can I Find Out More?
As you might have inferred already, the main reference within this field is Robert C. Martin. You can find a lot of extra information and examples at his object-oriented design website and the related Object Mentor website. You might also want to take a look at his best-practices books:
- Agile Software Development, Principles, Patterns and Practices (C++ and Java)
- Agile Principles, Patterns and Practices in C#
And last but not least, there is also a very interesting Hanselminutes podcast featuring Uncle Bob and the SOLID principles.
So, I hope you have enjoyed this introduction to the principles of OOD, and learnt a little in the process. I’ll be writing more best-practices stuff in the non-too-distant future ^_^.