11. January 2021 By Harun Sevinc
The SOLID design principles
What are the SOLID principles?
The SOLID principles are design specifications that are used in software development. The term was coined by Robert C. Martin and describes five design principles that specify how functions and data structures are arranged in classes and how these classes should be interconnected.
The goal of SOLID is to produce software that tolerates modifications, can be easily traced and that forms a basis for the components used in many software systems.
This post aims to provide an overview of the principles that is as comprehensible as possible. I won’t go into too much detail, as there are far more extensive publications and articles on the individual principles out there.
- SRP: Single responsibility principle
- OCP: Open-closed principle
- LSP: Liskov’s substitution principle
- ISP: Interface segregation principle
- DIP: Dependency inversion principle
SRP – Single responsibility principle
The single responsibility principle (SRP) is probably the most misunderstood of the SOLID principles. According to Martin, software developers tend to assume that each module should only perform one task. There is a principle of refactoring and splitting to the lowest level for functions with a large scope, but this isn’t what SRP is about. The general description of the SRP is:
‘There should never be more than one reason to modify a class.’
However, since software is written for customers/stakeholders or users in most cases and the notion of classes is too specific at this point, we recommend following Martin’s logic:
‘A module should be responsible for one actor and only one.’
As an example, you could take a class or module that provides functions for different business areas. If a change occurs in a business area that means that the structure of the class needs to change, this could impact the part of the class that is required by another part of the organisation. This creates dependencies that can get in the way of software development. This means it would make sense to design classes and methods in a way that results in only one actor being responsible for requirements and adaptations.
In summary, the SRP is mainly about functions and classes and their connection to actors that create requirements.
OCP – Open-closed principle
The open-closed Principle (OCP) was formulated by Bertrand Meyer:
‘A software entity should be open to extensions, but at the same time closed to modifications.’
Or in other words, the behaviour of a software entity should be able to be extended in a way that means it doesn’t need to be modified. You could now say that if a modification to the software leads to the need for massive intervention in it, then the architecture of the software has failed.
So how do you extend software without modifying it? When Martin applied Meyer’s principle in the 1990s, he implemented it using a different technical approach. Meyer’s solution was to use inheritance, which is well known in the object-oriented world. This was an important factor in terms of being able to maintain and extend software at the time. Let’s take two classes of car as an example – the passenger car and the sports car. The sports car would inherit all of the important properties and functionalities of the passenger car. Specific functionalities and properties would then be added to the sports car. The dependency here only goes in one direction and that’s a good thing.
To go a step further, you can extend this example by using interfaces, which is exactly what Martin does in his.
OCP, in its extended version, is thus a useful and now widely used principle. Although inheritance results in partitioning, there is no true multiple inheritance in Java, for example. Instead, interfaces are used, which in turn can be increasingly implemented in a class. The aim and purpose should always be to protect classes of a higher hierarchy from modifications in classes of a lower hierarchy.
LSP – Liskov’s substitution principle
LSP was defined by Barbara Liskov as follows:
‘If for every object o1 of type S there exists an object o2 of type T such that for all programs P defined in T the behaviour of P is unchanged when o1 is substituted for o2, then S is a subtype of T.’
In principle, this means that with inheritance, the subclass must contain all of the properties of the superclass at all times and can be used by the superclass. The subclass must not contain any modifications to the functionalities of the superclass, but new functionalities can be added to it.
Let’s go back to the passenger car example. We have the superclass passenger car, which provides functions such as acceleration and braking. We now also have two subclasses such as the sports car and the compact car. Both subclasses must be able to use the methods of the superclasses at any time. However, the subclass may have extended properties. For instance, the sports car could still have the ‘Enable sports mode’ function, which manipulates the driving properties of the vehicle.
LSP thus goes one step further than OCP and imposes conditions on subclasses through the superclass in the case of multiple inheritance. You can delve deeper into the different variances here (invariance, covariance and so on) to get a better understanding of LSP.
ISP – Interface segregation principle
The ISP is used to avoid forcing users to implement parts of interfaces that aren’t needed. This should result in interfaces not becoming too large and shrinking to fit a specific use.
This is easy to see if we put it in a diagram. The figure below shows that the superclass (class) has implemented multiple operations (Op1, Op2, Op3). However, User1 only uses Op1, and User2 and User3 only use Op2 and Op3 respectively. In this case, User1, although not invoking the operation, would depend on Op2 and Op3. For example, if you were to change something about the implementation of Op2 in the superclass, you would have to compile and deploy everything from scratch for User1 as well. This is despite the fact that there is effectively no change to the modules used by User1.
The solution to this problem would be to split the operations into interfaces, as shown in the next figure. In a statically typed language – say Java – the source code of User1 would only depend on the classes User1Op1 and the corresponding Op1, and no longer on Class.
The ISP is designed to prevent module dependencies that carry unnecessary load from causing completely unexpected problems. Mitigating dependencies ensures that code modifications cannot lead to complex and extensive changes or problems. The extra effort of adding another layer after the fact ensures that the architecture is better able to handle modifications.
DIP – Dependency inversion principle
The last of the five SOLID principles is the dependency inversion principle (DIP). DIP is designed to make clear that systems in which source code dependencies refer exclusively to abstractions rather than to concretions are the most flexible.
In Java, this means that statements should only refer to source modules – such as interfaces, abstract classes or modules that provide any other form of abstraction – when using use, import and include. This is to ensure that dependencies on concrete modules are not created.
However, using this principle as a rule is anything but realistic, as software systems also depend on concrete entities. In Java, for instance, the string class is designed to be very concrete. Trying to make them abstract wouldn’t make much sense. The dependency on the string object shouldn’t be avoided here either.
Based on this argument, DIP should mainly refer to the parts of the software that are being worked on and are obviously accessible for modification.
What can we take from this?
In summary, each of the principles can have a significant impact on developing good software architecture. For this, the principles must be interpreted correctly and used in the context of the software.
I wouldn’t call the principles the cornerstones of good software architecture, but rather a solid foundation that every software developer and architect should have internalised. You can see that some of the principles are recursively connected or build on each other. SOLID principles appear again and again in higher architectural topics and that makes it crucial for all aspiring software developers and architects to know them.
Would you like to learn more about exciting topics from the world of adesso? Then check out our latest blog posts.