`

# Explain SOLID Principles

341

The SOLID principles are a set of design principles aimed at making software designs more understandable, flexible, and maintainable. These principles were introduced by Robert C. Martin, also known as Uncle Bob. The acronym SOLID stands for:

1. Single Responsibility Principle (SRP)
2. Open/Closed Principle (OCP)
3. Liskov Substitution Principle (LSP)
4. Interface Segregation Principle (ISP)
5. Dependency Inversion Principle (DIP)

Let's go through each principle with a Ruby implementation example:

1. Single Responsibility Principle (SRP)

A class should have only one reason to change, meaning it should only have one job or responsibility.

Example:
class Invoice
  def initialize(customer)
    @customer = customer
  end

  def calculate_total
    # calculation logic
  end
end

class InvoicePrinter
  def print(invoice)
    # printing logic
  end
end
Here, `Invoice` is responsible for invoice-related data and calculations, and `InvoicePrinter` is responsible for printing the invoice. Each class has a single responsibility.

2. Open/Closed Principle (OCP)

Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.

Example:
class Order
  def initialize
    @items = []
  end

  def add_item(item)
    @items << item
  end

  def total_price
    @items.sum(&:price)
  end
end

class Discount
  def apply(order)
    raise NotImplementedError, "Subclasses must implement"
  end
end

class ChristmasDiscount < Discount
  def apply(order)
    order.total_price * 0.1
  end
end

class NewYearDiscount < Discount
  def apply(order)
    order.total_price * 0.2
  end
end
Here, the `Order` class is closed for modification but can be extended with different discount strategies by subclassing `Discount`.

3. Liskov Substitution Principle (LSP)

Objects of a superclass should be replaceable with objects of a subclass without affecting the functionality.

Example:
class Bird
  def fly
    # fly logic
  end
end

class Sparrow < Bird
  def fly
    super
  end
end

class Penguin < Bird
  def fly
    raise "Penguins can't fly"
  end
end

Here, `Penguin` violates the LSP because it can't fly, which contradicts the behavior expected from the `Bird` superclass. A better approach is to redesign the hierarchy so that flight capability is separated from the general bird characteristics.

4. Interface Segregation Principle (ISP)

Clients should not be forced to depend on interfaces they do not use.

Example:
module Printer
  def print_document
    raise NotImplementedError, "This method should be overridden"
  end
end

module Scanner
  def scan_document
    raise NotImplementedError, "This method should be overridden"
  end
end

class AllInOnePrinter
  include Printer
  include Scanner

  def print_document
    # print logic
  end

  def scan_document
    # scan logic
  end
end

class SimplePrinter
  include Printer

  def print_document
    # print logic
  end
end
Here, `SimplePrinter` only includes the `Printer` module because it does not need to implement scanning functionality, adhering to the ISP.

5. Dependency Inversion Principle (DIP)

High-level modules should not depend on low-level modules. Both should depend on abstractions (e.g., interfaces). Abstractions should not depend on details. Details should depend on abstractions.

Example:
class FileStorage
  def save(data)
    # save data to file
  end
end

class DatabaseStorage
  def save(data)
    # save data to database
  end
end

class DataSaver
  def initialize(storage)
    @storage = storage
  end

  def save(data)
    @storage.save(data)
  end
end

file_storage = FileStorage.new
db_storage = DatabaseStorage.new

data_saver = DataSaver.new(file_storage)
data_saver.save("some data")

data_saver = DataSaver.new(db_storage)
data_saver.save("some other data")
Here, `DataSaver` depends on an abstraction (storage) and not on a specific implementation, adhering to the DIP. This allows for flexible and interchangeable storage strategies.

By following these principles, your Ruby code will be more modular, easier to maintain, and adaptable to change.