Features of Good Design 2 - Solid Principles
date
Mar 12, 2022
slug
features-of-good-design-solid
status
Published
tags
coding
programming
Design
architecture
clean code
solid
summary
Software design - solid principles for clean code/architecture
type
Post
The SOLID principles were introduced by Robert C. Martin in his 2000 paper “Design Principles and Design Patterns.” These concepts were later built upon by Michael Feathers, who introduced us to the SOLID acronym.
Design principles encourage us to create more maintainable, understandable, and flexible software. Consequently, as our applications grow in size, we can reduce their complexity and save ourselves a lot of headaches further down the road.
Single Responsibility Principle
A class should have just one reason to change.
Try to make every class responsible for a single part of the functionality provided by the software, and make that responsibility entirely encapsulated by (you can also say hidden within) the class.
Open/Closed Principle
Classes should be open for extension but closed for modification. The main idea of this principle is to keep existing code from breaking when you implement new features.
Liskov Substitution Principle
When extending a class, remember that you should be able to pass objects of the subclass in place of objects of the parent class without breaking the client code.
This means that the subclass should remain compatible with the behavior of the superclass. When overriding a method, extend the base behavior rather than replacing it with something else entirely.
Unlike other design principles which are wide open for interpretation, the substitution principle has a set of formal requirements for subclasses, and specifically for their methods.
- Parameter types in a method of a subclass should match or be more abstract than parameter types in the method of the superclass.
- The return type in a method of a subclass should match or be a subtype of the return type in the method of the superclass. As you can see, requirements for a return type are inverse to requirements for parameter types.
- A method in a subclass shouldn’t throw types of exceptions that the base method isn’t expected to throw. In other words, types of exceptions should match or be subtypes of the ones that the base method is already able to throw. This rule comes from the fact that try-catch blocks in the client code target specific types of exceptions that the base method is likely to throw. Therefore, an unexpected exception might slip through the defensive lines of the client code and crash the entire application.
- A subclass shouldn’t weaken post-conditions. Say you have a class with a method that works with a database. A method of the class is supposed to always close all opened database connections upon returning a value. You created a subclass and changed it so that database connections remain open so you can reuse them. But the client might not know anything about your intentions. Because it expects the methods to close all the connections, it may simply terminate the program right after calling the method, polluting a system with ghost database connections.
Interface Segregation Principle
Clients shouldn’t be forced to depend on methods they do not use.
- Try to make your interfaces narrow enough that client classes don’t have to implement behaviors they don’t need.
- According to the interface segregation principle, you should break down “fat” interfaces into more granular and specific ones. Clients should implement only those methods that they really need. Otherwise, a change to a “fat” interface would break even clients that don’t use the changed methods.
Dependency Inversion Principle
High-level classes shouldn’t depend on low-level classes. Both should depend on abstractions. Abstractions shouldn’t depend on details. Details should depend on abstractions.
- For starters, you need to describe interfaces for low-level operations that high-level classes rely on, preferably in business terms. For instance, business logic should call a method openReport(file) rather than a series of methods openFile(x) , readBytes(n) , closeFile(x) . These interfaces count as high-level ones.
- Now you can make high-level classes dependent on those interfaces, instead of on concrete low-level classes. This dependency will be much softer than the original one.
- Once low-level classes implement these interfaces, they become dependent on the business logic level, reversing the direction of the original dependency.