In the mid-1990s, Robert C. Martin gathered five principles for object-oriented class design, presenting them as the best guidelines for building a maintainable object-oriented system.
Michael Feathers attached the acronym SOLID to these principles in the early 2000s.
SOLID Class Design Principles
Single Responsibility Principle (SRP).
Open-closed principle (OCP)
Liskov Substitution Principle (LSP)
Interface Segregation Principle (ISP)
Dependency Inversion Principle (DIP)
Single Responsibility Principle (SRP)
The “S” in SOLID is for the Single Responsibility Principle. Classes should have one, and only one, reason to change. Keep your classes small and single-purpose.
Ex: Class ProductManager is performing 3 tasks: getting data from the database, processing data, and printing reports.
class ProductManager {
public void QueryDataFromDB();
public void ProcessData();
public void PrintReport();
}
The ProductManager class is performing 3 tasks: getting data from the database, processing data, and printing reports. One class performs many tasks, so it violates the Single Responsibility Principle.
According to the principle, we need to separate this class into 3 separate classes so that the code is easy to read, has few bugs, and is easy to maintain.
class ProductManager {
public void QueryDataFromDB();
}
class ReportLogic {
public void ProcessData();
}
class PrintService {
public void PrintReport();
}
Open-Closed Principle (OCP)
The “O” in SOLID is for the Open-Closed Principle. Design classes to be open for extension but closed for modification; you should be able to extend a class without modifying it. Minimize the need to make changes to existing classes
Ex: We have a ConnectionManager class that can be implemented to connect to database systems such as SQL Server, and MySQL.
class ConnectionManager {
public function doConnection(object $connection) {
if ($connection instanceof SqlServer) {
// connect with SqlServer
} elseif ($connection instanceof MySql) {
// connect with MySql
}
}
}
With the above class, if we want to support another database system like PostgreSQL, we must modify the class to support creating a connection to PostgreSQL, thus violating the Open-Closed Principle.
To not violate the above principle, we can correct it as follows:
abstract class Connection {
public abstract function doConnect();
}
class SqlServer extends Connection {
public function doConnect() {
// connect to SqlServer
}
}
class MySql extends Connection {
public function doConnect() {
// connect to MySql
}
}
class ConnectionManager {
public function doConnect(Connection $connection) {
$connection->doConnect();
// do something...
}
}
Liskov Substitution Principle( LSP)
The “L” in SOLID is for the Liskov Substitution Principle. Subtypes should be substitutable for their base types. From a client's perspective, override methods should not break functionality.
Ex: We define the Brid interface to have 3 functions: fly(), eat() and walk ().
interface Bird {
public function fly();
public function walk();
public function eat();
}
class Pigeon implements Bird {
public function fly() {
// do something
}
public function walk() {
// do something
}
public function eat() {
// do something
}
}
class Penguin implements Bird {
public function fly() {
// penguin can not fly.
}
public function eat() {
// do something
}
public function walk() {
// do something
}
}
In the case of the Pigeon class implementing Bird, it is correct, but the Penguin class implementing Bird violates the Liskov Substitution Principle because Penguin cannot fly.
We can fix it in the following way so as not to violate the Liskov Substitution Principle.
interface Bird {
public function eat();
}
interface FlyingBird extends Bird {
public function fly();
}
interface WalkingBird extends Bird {
public function walk();
}
class Pigeon implements FlyingBird, WalkingBird {
public function fly() {
// do something
}
public function eat() {
// do something
}
public function walk() {
// do something
}
}
class Penguin implements WalkingBird {
public function eat() {
// do something
}
public function walk() {
// do something
}
}
Interface Segregation Principle (ISP)
The “I” in SOLID is for the Interface Segregation Principle. Clients should not be forced to depend on methods they don’t use. Split a larger interface into several smaller interfaces.
Ex: The Bird interface defines many functions, but not every class that inherits this interface uses all the functions that have been defined. For example, Pigeon implementing Bird cannot use the fly function.
interface Bird {
public function fly();
public function walk();
public function eat();
}
We can divide the Bird interface according to each function as follows:
interface Bird {
public function eat();
}
interface FlyingBird extends Bird {
public function fly();
}
interface WalkingBird extends Bird {
public function walk();
}
Dependency Inversion Principle (DIP)
The “D” in SOLID is for the Dependency Inversion Principle. High-level modules should not depend on low-level modules; both should depend on abstractions. Abstractions should not depend on details; details should depend on abstractions.
Why does SOLID help programming be more efficient?
Easy to understand: The clear division of functions of each class makes reading the code easier to understand.
Easy to update & maintain: Because it is easy to understand, updating or maintaining will be easier when a bug occurs.
Reuse: because the components do not depend on each other, each module can be separated and these modules can be reused for future projects.
[Reference Source]
- https://www.toptal.com/software/single-responsibility-principle (image)
- https://springframework.guru/principles-of-object-oriented-design/dependency-inversion-principle (image)
- https://www.globalnerdy.com/2009/07/15/the-solid-principles-explained-with-motivational-posters
- https://wit-computing-msc-2017.github.io/agile/topic08-srp-and-tdd-4/index.html