Object Oriented Design Principles
23 Feb 2022This blog is based on series of articles by Uncle Bob
What is Bad Design?
As per Robert Martin, below are the 3 factors which make up a bad design.
Rigidity:It is hard to change code because every change affects too many other parts of the system.Fragility:When you make a change, unexpected parts of the system break.
Immobility:It is hard to reuse in another application because it cannot be disentangled from the current application.
The design principles discussed are aimed to preventing Bad Designs.
The Single Responsibility Principle
The single responsibility principle states that each class should have only one responsibility and one single purpose. This implies that the class will do only one job, and will have only one reason to be changed. The objects shouldn't have too much information. This is one of the core principles of OOD. It makes software easier to maintain and prevents any unexpected side-effects.
The Open/Closed Principle
The principle states that software entities should be open for extension, but closed for modification.
Open: A component is 'open' if it's available for extension - add data members, create new template, etc.
Closed: A compnent is closed if it's available for use by other components but may not itself be changed - change access modifiers.
In large complex systems, changes occur due to performance issues, change requriments, etc. Making changes to components often result in
cascades of changes dependent components. The open/closed principle says that components should never change, only be extended to meet changing requirements.
It is very difficult to build components that don't change, but only support extension. One of the ways to deal with this is represent the component by an abstract interface e.g. an abstract class, which provides an interface which derived classes implements.
When we program to abstract interfaces, changes to derived classes will not break any client code. Of course we cannot change the interface definition.
Abstract interfaces directly support the Open/Closed principle. They must be extended, but are closed for modifications.
The Liskov Substitution Principle
The principle states that functions that use pointers or references statically typed to some base class must be able to use objects of classes derived from the base through those pointers or references
without any knowledge of specialized to the derived class.
This principle is helpful in designing loosely coupled systems. The base class provides a protocol for clients to use regardless of what derived class is reciving the client's messages.
Although it looks pretty straigh forward, below are the reasons that the hierarchy of classses will fail to satisfy this principle if:
- The base class does not make it's destructor virtual.
- Derived classes redefine non-virtual member functions of the base.
- Virtual functions are overloaded.
- Clients use dynamic cast to access derived class extensions.
The Interface Segregation Principle
The principle states that clients should ot be forced to depend upon interfaces they do not use.
Interfaces are created to satisfy the needs of the clients. When a component has several different clients, it is tempting to provide a large interface that satisfies the needs of all the clients.
It is a much better design to have the component support multiple interfaces, one for each client. Otherwise if we have to change the interface, we affect those clients that do not use the feature we change.
The open/closed principle states that we should never change an interface, however in reality, not all interfaces can be abstract.
When such situation arises, it's important to keep in mind the impact of changing on interface. This principle states that we should push the interfaces down the hierarchy to the clients that really need them.
The Dependency Inversion Principle
The principle states that high level components should not depend on low level components. Instead, both should depend on abstractions.
Abstractions should not depend upon details, details should depend upon abstractions.
Complex systems generally need to be structures into layers. But if not done carefully, the top levels tend to depend on lower levels, and a change in the lower level need to be surfaced all the way to the top.
Hence, depending on abstraction, instead of implementation helps in resolving this issue.