The SOLID principles are a set of five design guidelines that help developers create more maintainable, understandable, flexible, and testable software. Introduced by Robert C. Martin, also known as Uncle Bob, these principles have become a cornerstone of object-oriented programming and design.

SOLID is an acronym, with each letter standing for the first letter of a principle.

The SOLID principles

Single Responsibility

A class should have one, and only one, reason to change. So a class should have only one responsibility.

// ❌ This class does multiple responsibility (instance, save, and send email)
class User {
public User(String name, String email) { /* */ }
public void saveUser() { /* save user to database */ }
public void sendEmail() { /* */ }
}
// ✅ 1 class = 1 responsibility
class User {
public User(String name, String email) { /* */ }
}
class UserDatabaseManager {
public void saveUser(User user) { /* */ }
}
class EmailSender {
public void sendEmail(User user, String message) { /* */ }
}

Note: this definition may vary. Some developers prefer to define it as:

Separate code that supports different actors.

Code should have a reason to change based on the actors involved in the project. Therefore, you should organize the code according to the different actors.

Open/Closed

Objects or entities should be open for extension but closed for modification.

// ❌
class SpeedCalculator {
public BigDecimal calculateSpeed(Vehicle vehicle) {
if (vehicle instanceof Car) {
// calculate speed here…
} else if (vehicle instanceof Airplane) {
// calculate speed here…
} else if (vehicle instanceof Bicycle) {
// calculate speed here…
}
// 👎 This code is bad because if we want to add a new vehicle,
// we will have to modify this method
}
}
// ✅ This approach is extensible.
// If we want to add a new vehicle, we only have to add one class and that's it !
interface Vehicle {
public BigDecimal calculateSpeed();
}
class Car implements Vehicle {
public BigDecimal calculateSpeed() { /* */ }
}
class Airplane implements Vehicle {
public BigDecimal calculateSpeed() { /* */ }
}

Liskov Substitution

Let q(x) be a property provable about objects of x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T.

In other words: Subclasses should be able to substitute their parent classes without affecting the behavior of the program.

// ❌
abstract class Bird {
abstract public void fly();
}
class Pigeon extends Bird {
@Override
public void fly() { /* */ }
}
// 👎 The principle is violated because Penguin does not support all the methods it inherits
class Penguin extends Bird {
@Override
public void fly() {
throw new FlyException("Penguins can not fly");
}
}
// --------
// This code leads to a bug:
class BirdActivityTracker {
public void trackFlyingActivity(){
List<Bird> birds = new ArrayList<>();
birds.add(new Pigeon());
birds.add(new Penguin());
birds.forEach(bird -> {
bird.fly(); // 👎 BUG !
});
}
}
// ✅
abstract class Bird {}
abstract class FlyingBird extends Bird {
abstract public void fly();
}
class Penguin extends Bird {}
class Pigeon extends FlyingBird {
@Override
public void fly() { /* */ }
}

Interface Segregation

A client should never be forced to implement an interface that it doesn’t use, or clients shouldn’t be forced to depend on methods they do not use.

// ❌
interface UserOperation {
public User createUser(String name);
public void deleteUser(int id);
public User getUser(int id);
public User updateUser(int id);
public User[] getUsers();
}
class Admin implements UserOperation { /* */ }
// 👎 This class Guest should not support the operations "create/delete/update"
// We have to implements ALL these methods even we don't need them
class Guest implements UserOperation { /* */ }
// ✅ Let's split UserOperation in smaller pieces
interface UserCreation {
public User createUser(String name);
}
interface UserDeletion {
public void deleteUser(int id);
}
interface UserRetrieval {
public User getUser(int id);
public User[] getUsers();
}
interface UserUpdate {
public User updateUser(int id);
}
// Here, Admin and Guest implement only the necessary methods
class Admin implements UserCreation, UserDeletion, UserRetrieval, UserUpdate { /* */ }
class Guest implements UserRetrieval { /* */ }

Dependency Inversion

Entities must depend on abstractions, not on concretions. It states that the high-level module must not depend on the low-level module, but they should depend on abstractions.

// ❌ The Authentication class is tightly coupled with the MySQLDatabase class
class Authentication {
private MySQLDatabase database;
public Authentication(MySQLDatabase database) {
this.database = database;
}
}
// ✅ It is preferable to depend on the abstraction only
interface Database {
public void connect();
public void disconnect();
}
class MySQLDatabase implements Database { /* */ }
class PostgreSQLDatabase implements Database { /* */ }
// 👍 The Authentication class can now work with any database type
class Authentication {
private Database database;
public Authentication(Database database) {
this.database = database;
}
}

The STUPID principles

The STUPID principles are a set of anti-patterns in software design that you should avoid.

Singleton

Ensures a class has only one instance, but overusing it can lead to global state and tight coupling, making testing and maintenance difficult.

Tight Coupling

When modules are highly dependent on each other, changes in one module require changes in another, reducing flexibility and reusability.

It refers to Dependency Inversion from SOLID principle.

Untestability

Code that is hard to test, often due to tight coupling or reliance on global state, making it difficult to write unit tests.

Premature Optimization

Optimizing code before it’s necessary, which can lead to complex and hard-to-maintain code without significant performance benefits.

Indescriptive Naming

Using unclear or ambiguous names for variables, methods, or classes, making the code hard to understand and maintain.

// ❌
class User {
private String n;
private String emA;
private String ph;
}
// ✅
class User {
private String pseudo;
private String emailAddress;
private String phoneNumber;
}

Duplication

Repeating code in multiple places instead of reusing it, leading to more maintenance work and potential inconsistencies.

Other notes

To be a proficient software developer, it’s essential to understand both the SOLID and STUPID principles.

You may encounter other principles such as KISS (Keep It Simple, Stupid) and DRY (Don’t Repeat Yourself), which stem from the SOLID principles.

Happy coding! 😊


Recommended articles