Skip to content

S.O.L.I.D. Principles

The SOLID principles are five design principles intended to make object-oriented designs more understandable, flexible, and maintainable.

A class should do only one thing that is core to it. The idea is that the fewer responsibilities a class has, the less likely it will need to change in the future.

Benefits:

  • Easier to understand and maintain
  • Reduces the impact of changes
  • Improves code reusability

Instead of modifying existing base classes, we should extend them through inheritance to add new features. This prevents breaking existing functionality while allowing growth.

Implementation Strategy:

  • Use inheritance to extend behavior
  • Leverage interfaces and abstract classes
  • Avoid modifying stable code

Bad Example (Violates OCP):

class AreaCalculator {
public double calculateArea(Object shape) {
if (shape instanceof Circle) {
Circle circle = (Circle) shape;
return Math.PI * circle.radius * circle.radius;
} else if (shape instanceof Rectangle) {
Rectangle rect = (Rectangle) shape;
return rect.width * rect.height;
}
// Adding new shape requires modifying this method
return 0;
}
}

Good Example (Follows OCP):

Define an interface that shapes can implement:

interface Shape {
double calculateArea();
}
class Circle implements Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
public double calculateArea() {
return Math.PI * radius * radius;
}
}
class Rectangle implements Shape {
private double width;
private double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
public double calculateArea() {
return width * height;
}
}

Now we can add new shapes without modifying existing code:

class Triangle implements Shape {
private double base;
private double height;
public Triangle(double base, double height) {
this.base = base;
this.height = height;
}
public double calculateArea() {
return 0.5 * base * height;
}
}

If you have a subclass (e.g., BigDog extends Dog), you should be able to use a BigDog object anywhere a Dog object is expected without breaking the codebase.

Example:

Dog myDog = new BigDog(); // Should work seamlessly

Requirements:

  • Don’t change method signatures in subclasses
  • Maintain return types from the base class
  • Preserve the expected behavior of the parent class

Instead of creating large interfaces with many methods, split them into smaller, focused interfaces (3-5 methods each). This prevents implementing classes from being burdened with irrelevant methods.

Bad Example:

interface Organism {
void fly();
void walk();
}
class Bird implements Organism {
void fly() { /* implementation */ }
void walk() { /* birds can't walk! */ }
}

Good Example:

interface Flyable {
void fly();
}
interface Walkable {
void walk();
}
class Bird implements Flyable { /* ... */ }
class Dog implements Walkable { /* ... */ }

Write high-level classes that depend on interfaces rather than concrete low-level classes. This reverses the traditional dependency direction.

Example Scenario:

Instead of StockMarketData directly using a specific database implementation (SQL), it should depend on a Database interface:

interface Database {
void insert(Data data);
Data retrieve(String id);
}
class SQLDatabase implements Database { /* ... */ }
class MongoDatabase implements Database { /* ... */ }
class StockMarketData {
private Database db;
// Dependency is injected, not created
public StockMarketData(Database db) {
this.db = db;
}
}

Design Pattern Categories (GAMMA Categorization)

Section titled “Design Pattern Categories (GAMMA Categorization)”

Design patterns, as defined by the Gang of Four (GoF), are organized into three main categories:

Purpose: Deal with object creation mechanisms

  • Focuses on how objects are constructed
  • Explicit creation: Using constructors directly
  • Implicit creation: Using patterns like Dependency Injection (DI), reflection, factories

Learn more about Creational Patterns →

Purpose: Concern the composition of classes and objects

  • Focus on how classes and objects are structured
  • Emphasize good API design
  • Help create interfaces that are convenient to use
  • Enable replication and adaptation of interfaces

Learn more about Structural Patterns →

Purpose: Define how objects interact and communicate

Each behavioral pattern addresses a specific problem related to object collaboration:

  • Chain of Responsibility
  • Command
  • Interpreter
  • Iterator
  • Mediator
  • Memento
  • Observer
  • State
  • Strategy
  • Template Method
  • Visitor
  • Null Object

Learn more about Behavioral Patterns →