SOLID principles

Author: Dmitry Ro
Updated: 2023-12-12

The SOLID principles are a set of five design principles that help developers create more maintainable, scalable, and flexible software. These principles were introduced by Robert C. Martin, also known as Uncle Bob, and have become fundamental in the world of object-oriented programming.

What Are the SOLID Principles?

Each letter in SOLID represents a different principle:

  1. S - Single Responsibility Principle (SRP): This principle advocates for a class to have only one reason to change. In essence, a class should have a single responsibility or function within the software system. When a class takes on multiple responsibilities, it becomes harder to maintain, test, and reason about.
# Example of violating SRP
class Employee
  def calculate_pay
    # Calculate employee pay
  end

  def generate_report
    # Generate employee report
  end
end

# Refactored code following SRP
class Employee
  def calculate_pay
    # Calculate employee pay
  end
end

class ReportGenerator
  def generate_report(employee)
    # Generate employee report
  end
end
            
  1. O - Open/Closed Principle (OCP): The Open/Closed Principle suggests that software entities should be open for extension but closed for modification. This means that you should be able to extend the behavior of a module without altering its source code. By using techniques like inheritance, interfaces, and polymorphism, you can achieve this principle.

# Violation of OCP
class Shape
  def area
    raise NotImplementedError, 'Subclasses must implement this method'
  end
end

class Square < Shape
  attr_accessor :side_length

  def initialize(side_length)
    @side_length = side_length
  end

  def area
    @side_length * @side_length
  end
end

# Adhering to OCP using abstraction
class Shape
  def area
    raise NotImplementedError, 'Subclasses must implement this method'
  end
end

class Square < Shape
  def initialize(side_length)
    @side_length = side_length
  end

  def area
    @side_length * @side_length
  end
end

class Circle < Shape
  def initialize(radius)
    @radius = radius
  end

  def area
    Math::PI * @radius * @radius
  end
end

            
  1. L - Liskov Substitution Principle (LSP): Named after Barbara Liskov, this principle states that objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program. In simpler terms, derived classes must be substitutable for their base classes without altering the desired functionality of the program.
# Violation of LSP
class Bird
  def fly
    'Flying'
  end
end

class Ostrich < Bird
  def fly
    raise NotImplementedError, 'Ostrich cannot fly'
  end
end

# Adhering to LSP
class Bird
  def fly
    raise NotImplementedError, 'Subclasses must implement this method'
  end
end

class Ostrich < Bird
  def fly
    'Ostrich cannot fly'
  end
end
            
  1. I - Interface Segregation Principle (ISP): ISP emphasizes that a client should not be forced to depend on methods it does not use. It encourages breaking down interfaces into smaller, specific ones, tailored to the needs of the clients. This prevents the creation of “fat” interfaces that clients have to implement unnecessary methods from.
# Example of violating SRP
class Employee
  def calculate_pay
    # Calculate employee pay
  end

  def generate_report
    # Generate employee report
  end
end

# Refactored code following SRP
class Employee
  def calculate_pay
    # Calculate employee pay
  end
end

class ReportGenerator
  def generate_report(employee)
    # Generate employee report
  end
end
  1. D - Dependency Inversion Principle (DIP): DIP focuses on reducing dependencies between modules or classes by relying on abstractions rather than concrete implementations. High-level modules should not depend on low-level modules but rather both should depend on abstractions. This principle promotes flexibility and makes the code more testable and maintainable.

# Violation of DIP
class LightSwitch
  def initialize(light)
    @light = light
  end

  def toggle
    @light.turn_on_or_off
  end
end

class Light
  def turn_on_or_off
    # Turn on or off the light
  end
end

# Adhering to DIP by using abstraction
class SwitchableDevice
  def turn_on_or_off
    raise NotImplementedError, 'Subclasses must implement this method'
  end
end

class Light < SwitchableDevice
  def turn_on_or_off
    # Turn on or off the light
  end
end

class LightSwitch
  def initialize(device)
    @device = device
  end

  def toggle
    @device.turn_on_or_off
  end
end

            

Why Are the SOLID Principles Important?

  1. Maintainability: By following SOLID principles, code becomes easier to understand, maintain, and extend. This results in fewer bugs and faster development cycles.

  2. Flexibility: Adhering to SOLID principles allows for easier modification and adaptation of code. New functionalities can be added without altering existing, working code.

  3. Scalability: As software systems grow, adhering to SOLID principles ensures that the system remains manageable. It becomes simpler to add new features or refactor existing ones.

  4. Testability: Writing unit tests for code that follows SOLID principles is more straightforward. Code becomes isolated and less tightly coupled, making it easier to test individual components.

Implementation of SOLID Principles

Applying SOLID principles requires a deep understanding of the principles themselves and their implications. It involves practices like abstraction, design patterns (such as Factory, Strategy, and Adapter patterns), and careful consideration of class relationships.

Developers often use code reviews, refactoring, and design discussions within their teams to ensure that SOLID principles are followed effectively.

In conclusion, the SOLID principles provide a framework for writing clean, maintainable, and robust code. While adhering strictly to these principles might not always be feasible or necessary in every situation, understanding and applying them where applicable greatly contributes to the overall quality and longevity of software systems.