Software Design Principles with Ruby (Applying SOLID)
Daleska

Daleska @paltadash

Joined:
Apr 18, 2024

Software Design Principles with Ruby (Applying SOLID)

Publish Date: Apr 19
2 0

“Clean code is its own best documentation.” — Steve McConnell

When we start programming, it's common to focus on just making it work. But true skill lies in making it maintainable, readable, and easy to extend over time.

That's where the SOLID principles come in. In this article, I’ll show you what they are, why they matter, and how to apply them with a real Ruby example to see how powerful they can be.


🧱 What is SOLID?

SOLID is a set of five object-oriented design principles introduced by Robert C. Martin to improve code quality and make it scalable and maintainable.

The Principles:

  • S - Single Responsibility Principle (SRP)
  • O - Open/Closed Principle (OCP)
  • L - Liskov Substitution Principle (LSP)
  • I - Interface Segregation Principle (ISP)
  • D - Dependency Inversion Principle (DIP)
  1. SRP - Single Responsibility Principle: A class should have only one reason to change — a single responsibility.

  2. OCP - Open/Closed Principle: Software should be open for extension but closed for modification. You should be able to add new functionality without changing existing code.

  3. LSP - Liskov Substitution Principle: Subclasses should be substitutable for their base classes without altering the correctness of the program.

  4. ISP - Interface Segregation Principle: Clients should not be forced to depend on interfaces they do not use. Prefer small, specific interfaces over large ones.

  5. DIP - Dependency Inversion Principle: High-level modules should not depend on low-level modules. Both should depend on abstractions.


📚 Case Study: Report Generator

Let’s imagine we are building an app that generates reports in multiple formats. We start with PDF and HTML, but then Markdown and DOCX get added.

Let’s see how to apply SOLID to this scenario.

❌ Version 1: Rigid and Hard to Scale

class Report
  attr_reader :title, :content

  def initialize(title, content)
    @title = title
    @content = content
  end

  def generate_pdf
    puts "Generating PDF for #{@title}"
  end

  def generate_html
    puts "Generating HTML for #{@title}"
  end
end

Enter fullscreen mode Exit fullscreen mode

This version works, but it violates two major principles:

  • SRP: Report has two responsibilities: storing data and generating formats.

  • OCP: To add a new format (e.g. DOCX), you must modify this class.


✅ Refactored: SOLID Principles Applied

# SRP (Single Responsibility Principle):
# This class is only responsible for storing the report's data.
class Report
  attr_reader :title, :content

  def initialize(title, content)
    @title = title
    @content = content
  end
end

# DIP (Dependency Inversion Principle):
# Abstract class that defines an interface for formatters.
# High-level classes will depend on this abstraction.
class ReportFormatter
  def format(report)
    raise NotImplementedError, 'This method must be implemented'
  end
end

# SRP and LSP
class PDFFormatter < ReportFormatter
  def format(report)
    puts "Generating PDF for #{report.title}"
  end
end

# SRP and LSP
class HTMLFormatter < ReportFormatter
  def format(report)
    puts "Generating HTML for #{report.title}"
  end
end

# OCP, SRP, LSP
class MarkdownFormatter < ReportFormatter
  def format(report)
    puts "# #{report.title}\n\n#{report.content}"
  end
end

# Client using the system
# DIP: Does not depend on a specific class, only on the abstraction
report = Report.new("Sales Report", "This is the content of the report")

formatter = HTMLFormatter.new
formatter.format(report)

formatter = PDFFormatter.new
formatter.format(report)

formatter = MarkdownFormatter.new
formatter.format(report)
Enter fullscreen mode Exit fullscreen mode

What did we improve?

SRP: Report only handles data, not formatting.
OCP: We can add MarkdownFormatter without modifying any existing classes.ninguna clase existente.
LSP: Any formatter can be used as a ReportFormatter without breaking the system.
DIP: The client depends on the abstraction (ReportFormatter), not on specific implementations.

Comments 0 total

    Add comment