# SOLID Design Principles

## Summary


In the world of object-oriented programming (OOP), there are many design guidelines, patterns or principles. Five of these principles are usually grouped together and are known by the acronym **SOLID**. While each of these five principles describes something specific, they overlap as well such that adopting one of them implies or leads to adopting another.

Programming in strict conformance to SOLID is generally not expected. However, programmers must be aware of SOLID and use them depending on the context. 

In general, SOLID helps us manage code complexity. It leads to more maintainable and extensible code. Even with big change requests, it's easier to update the code.

## Discussion

### What problems are solved or avoided by applying SOLID?

Software may start with a clean and elegant design but over time they become hard to maintain, often requiring costly redesigns. Robert Martin, who's credited with writing down the SOLID principles, points out some symptoms of rotting design due to improperly managed dependencies across modules: 

    + **Rigidity**: Implementing even a small change is difficult since it's likely to translate into a cascade of changes.
    + **Fragility**: Any change tends to break the software in many places, even in areas not conceptually related to the change.
    + **Immobility**: We're unable to reuse modules from other projects or within the same project because those modules have lots of dependencies.
    + **Viscosity**: When code changes are needed, developers will prefer the easier route even if they break existing design.Antipatterns and improper understanding of design principles can lead to *STUPID* code: Singleton, Tight Coupling, Untestability, Premature Optimization, Indescriptive Naming, and Duplication. SOLID can help developers stay clear of these. 

The essence of SOLID is managing dependencies. This is done via interfaces and abstractions. Modules and classes should not be tightly coupled.


### As a beginner, what are some things to keep in mind when applying SOLID?

SOLID is really a guideline and not a rule. They work in many cases but they're not guaranteed to always work. 

Just knowing the principles will not turn a bad programmer into a good one. This means that programmers need to understand why these principles make sense. They need to apply them with judgement. If they see code that violates these principles, they must try to see if the violations can be justified. If not, they can apply one or more principles to improve the code. Practice is the key. 


### Could you explain the Single Responsibility Principle?

A class or a module must have a specific responsibility and nothing more. Put it another way, it should change for only one reason. We can say that the responsibility is encapsulated within the class and there's stronger cohesion within the class. 

For example, an Automobile class can start or stop itself but the task of washing it belongs to the CarWash class. In another example, a Book class has properties to store its own name, author and text. But the task of printing the book must belong to the BookPrinter class. The BookPrinter class might print to console or another medium but such dependencies are removed from the Book class. 

When SRP is followed, testing is easier. With a single responsibility, the class will have fewer test cases. Less functionality also means less dependencies to other modules or classes. It leads to better code organization since smaller and well-purposed classes are easier to search. 


### Could you explain the Open-Closed Principle?

It should be possible to extend a module with additional behaviour without modifying it. This means that functions or base class methods should not get polluted with details of subclasses. A function that checks object types is a violation of OCP. This is because when a new object type is added, this function has to be modified to handle the new type. **Abstraction** is the key to getting OCP right. Two possible ways of doing this is polymorphism or templates/generics. 

OCP is important since classes may come to us via third-party libraries. We should be able to extend those classes without worrying if those base classes can support our extensions. But inheritance can lead to subclasses depending on base class implementation. To avoid this, use of **interfaces** is recommended. This additional abstraction leads to loose coupling. 

For example, a Car class might have methods AccelerateAudi, AccelerateBMW, and so on. This is a violation of OCP. Instead, we should have an ICar interface with method Accelerate. Each car subclass can implement this interface. 


### Could you explain the Liskov Substitution Principle?

This principle states that we can substitute a subclass for its base class without affecting behaviour. This avoids misusing inheritance. It helps us conform to the "is-a" relationship. We can also say that subclasses must fulfil a contract defined by the base class. In this sense, it's related to **Design by Contract** that was first described by Bertrand Meyer. For example, it's tempting to say that a circle is a type of ellipse but circles don't have two foci or major/minor axes. 

In another example, TRubberDucky is not exactly a duck because it can swim or quack but can't fly. So if a wildlife simulator instantiates a rubber duck and tries to make it fly, it will encounter an error. Thus, TRubberDucky as a subclass of TDuck is a violation of LSP. 

LSP is also related to Duck Typing. In fact, duck typing doesn't even require a type hierarchy via inheritance. 


### Could you explain the Interface Segregation Principle?

Segregate the interfaces so that subclasses are not required to use all of them. In other words, many client-specific interfaces are better than one general-purpose interface. 

For example, a single logging interface for writing and reading logs is useful for a database but not for a console. Reading logs make no sense for a console logger. 

Another example is an IMatrixOperations interface that has two methods for inverse and transpose operations. Problem is that for a matrix that's not regular, inverse operation is invalid. Such a matrix based on this interface would throw an exception for an inverse operation. Applying ISP, we would break this interface into separate ones, IRegularMatrixOperations (with inverse operation) and IMatrixOperations (that adds transpose operation). IMatrixOperations derives from IRegularMatrixOperations. 


### Could you explain the Dependency Inversion Principle (DIP)?

DIP says that modules should depend upon interfaces or abstract classes, not concrete classes. It's an inversion because implementations depend upon abstractions and not the other way round. Instead of high-level modules depending on low-level modules, let's decouple them and make use of abstractions. 

Let's say we instantiate a Windows98Machine object that's automatically created with a StandardKeyboard. Problem is that now we've coupled this keyboard with the machine. It's difficult to instantiate another machine with a different keyboard. This tight coupling also makes it difficult to test the Windows98Machine class. Instead, keyboard must be an interface. Any keyboard variant that uses this interface can be passed into the machine when the latter is constructed. 


### What are some common criticisms of SOLID?

While some criticisms are valid, often they arise due to misunderstanding of SOLID. People have called the principles vague. They lead to complex and unintelligible code since we end up with many interfaces and many small classes. DIP depends on dependency injection frameworks. LSP requires implementation inheritance. Most of these are not really true. SOLID principles are condensed software wisdom. Just knowing them is not a substitute for experience and understanding. 

SOLID focuses too much on dependencies. It encourages use of abstractions resulting in codebase littered with interfaces. To manage this problem, we end up introducing IoC containers and mocking frameworks, making the code more difficult to understand. SOLID produces testable code with low coupling but code is unintelligible. 

It's been said that OCP doesn't work in practice, particularly for domain entities or business classes that by nature change frequently. Following OCP would lead to long inheritance chains and also a violation of another design principle, "favour composition over inheritance". People have different interpretations about what should be open and what should be closed. This leads to inconsistencies. 

## Milestones

Oct  
1987

Barbara Liskov of MIT presents at a conference a paper titled *Data Abstraction and Hierarchy*. She uses the term "substitution property" and explains,

> Data abstractions provide the same benefits as procedures, but for data. Recall that the main idea is to separate what an abstraction is from how it is implemented so that implementations of the same abstraction can be substituted freely.

1988

Bertrand Meyer, the creator Eiffel programming language, publishes a book titled *Object-Oriented Software Construction*. Meyer is credited with introducing the Open-Closed Principle. 

1990

With growing codebases and complexity, object-oriented software design becomes popular to better manage software. But this change of programming paradigm from procedural to object-oriented does not automatically lead to clean code. Developers write large classes and methods. Code is duplicated. Old habits continue. There arises a need to guide developers design object-oriented software the right way. 

1994

Barbara Liskov and Jeannette Wing present the case that anyone making use of supertype objects should not see any difference in behaviour even if using a subtype object instead. This later becomes a principle of SOLID. This principle is also related to what Bertrand Meyer calls **Design by Contract**. 

Mar  
1995

On com.object group, Robert Martin mentions a number of commandments for OOP. Three of these would later become part of SOLID: Open-Closed, Liskov Substitution, Dependency Inversion. 

2000

Robert Martin writes in detail about four of the SOLID principles. The Single Responsibility Principle doesn't appear on this list. 

2002

Robert Martin publishes the book *Agile Software Development: Principles, Patterns, and Practices*. He explains in detail all the five SOLID principles under a section named "Agile Design". Thus, SOLID becomes an essential aspect of Agile methodology. Sometime later Michael Feathers coins the term SOLID as a useful way to remember the principles. 

2017

InterVenture publish extensions of SOLID. They identify six more principles. These are by no means universal in the industry but are worth studying. 

Sep  
2020

Paulo Merson notes in a blog article that SOLID was designed OOP. It may not exactly fit microservices. For microservices, he therefore proposes **IDEALS**: interface segregation, deployability (is on you), event-driven, availability over consistency, loose-coupling, and single responsibility.