design-patterns
This skill should be used when the user asks about "design patterns", "SOLID", "factory pattern", "singleton", "observer", "strategy", "decorator", "adapter", "facade", "command pattern", "builder pattern", "dependency injection", "composition", "Ruby patterns", or needs guidance on implementing design patterns in Ruby.
When & Why to Use This Skill
This Claude skill provides expert guidance and idiomatic Ruby implementations for essential software design patterns and SOLID principles. It is designed to help developers enhance code maintainability, scalability, and readability by applying architectural best practices such as Creational, Structural, and Behavioral patterns specifically tailored for the Ruby programming language.
Use Cases
- Refactoring legacy Ruby codebases to adhere to SOLID principles, ensuring classes are decoupled and follow the Single Responsibility Principle.
- Implementing flexible and extensible payment processing or notification systems using the Strategy and Open/Closed patterns.
- Simplifying complex subsystem interactions in large-scale applications by providing a unified interface through the Facade pattern.
- Constructing complex objects like dynamic SQL queries or multi-step document generators using the Builder and Factory patterns.
- Managing application-wide configurations and global states using idiomatic Ruby Singleton modules to ensure consistency across the environment.
| name | design-patterns |
|---|---|
| description | This skill should be used when the user asks about "design patterns", "SOLID", "factory pattern", "singleton", "observer", "strategy", "decorator", "adapter", "facade", "command pattern", "builder pattern", "dependency injection", "composition", "Ruby patterns", or needs guidance on implementing design patterns in Ruby. |
| version | 1.0.0 |
Design Patterns in Ruby
Idiomatic Ruby implementations of common design patterns.
SOLID Principles in Ruby
Single Responsibility Principle
# Bad: User handles too many concerns
class User
def save
validate!
Database.insert(self)
Mailer.send_welcome_email(self)
Analytics.track("user_created", self)
end
end
# Good: Each class has one reason to change
class User
def save
validate!
UserRepository.save(self)
end
end
class UserRegistrationService
def initialize(user)
@user = user
end
def call
@user.save
WelcomeMailer.deliver(@user)
Analytics.track("user_created", @user)
end
end
Open/Closed Principle
# Open for extension, closed for modification
class PaymentProcessor
def initialize(strategy)
@strategy = strategy
end
def process(amount)
@strategy.charge(amount)
end
end
class StripePayment
def charge(amount)
Stripe::Charge.create(amount: amount)
end
end
class PaypalPayment
def charge(amount)
Paypal::Payment.execute(amount)
end
end
# Add new payment methods without modifying PaymentProcessor
class CryptoPayment
def charge(amount)
Crypto::Transaction.send(amount)
end
end
Liskov Substitution Principle
# Subtypes must be substitutable for their base types
class Bird
def fly
raise NotImplementedError
end
end
# Bad: Penguin can't fly, violates LSP
class Penguin < Bird
def fly
raise "Penguins can't fly!"
end
end
# Good: Separate flying capability
module Flyable
def fly
raise NotImplementedError
end
end
class Bird; end
class Sparrow < Bird
include Flyable
def fly
"Flying high!"
end
end
class Penguin < Bird
def swim
"Swimming fast!"
end
end
Interface Segregation Principle
# Prefer small, focused interfaces
# Bad: One big interface
module Worker
def work; end
def eat; end
def sleep; end
end
# Good: Separate concerns
module Workable
def work
raise NotImplementedError
end
end
module Feedable
def eat
raise NotImplementedError
end
end
class Human
include Workable
include Feedable
def work = "Working..."
def eat = "Eating..."
end
class Robot
include Workable
def work = "Processing..."
# Robots don't need to eat
end
Dependency Inversion Principle
# Depend on abstractions, not concretions
# Bad: High-level module depends on low-level module
class Report
def initialize
@formatter = HTMLFormatter.new
end
def generate(data)
@formatter.format(data)
end
end
# Good: Depend on abstraction (duck typing in Ruby)
class Report
def initialize(formatter)
@formatter = formatter
end
def generate(data)
@formatter.format(data)
end
end
class HTMLFormatter
def format(data) = "<html>#{data}</html>"
end
class JSONFormatter
def format(data) = data.to_json
end
Report.new(HTMLFormatter.new).generate(data)
Report.new(JSONFormatter.new).generate(data)
Creational Patterns
Factory Method
class DocumentFactory
def self.create(type, **options)
case type
when :pdf then PDFDocument.new(**options)
when :word then WordDocument.new(**options)
when :html then HTMLDocument.new(**options)
else raise ArgumentError, "Unknown document type: #{type}"
end
end
end
document = DocumentFactory.create(:pdf, title: "Report")
Abstract Factory
class UIFactory
def create_button
raise NotImplementedError
end
def create_input
raise NotImplementedError
end
end
class DarkThemeFactory < UIFactory
def create_button = DarkButton.new
def create_input = DarkInput.new
end
class LightThemeFactory < UIFactory
def create_button = LightButton.new
def create_input = LightInput.new
end
def build_form(factory)
button = factory.create_button
input = factory.create_input
Form.new(button, input)
end
Builder
class QueryBuilder
def initialize
@select = "*"
@from = nil
@where = []
@order = nil
@limit = nil
end
def select(*columns)
@select = columns.join(", ")
self
end
def from(table)
@from = table
self
end
def where(condition)
@where << condition
self
end
def order(column, direction = :asc)
@order = "#{column} #{direction.upcase}"
self
end
def limit(n)
@limit = n
self
end
def to_sql
sql = "SELECT #{@select} FROM #{@from}"
sql += " WHERE #{@where.join(' AND ')}" if @where.any?
sql += " ORDER BY #{@order}" if @order
sql += " LIMIT #{@limit}" if @limit
sql
end
end
query = QueryBuilder.new
.select(:id, :name, :email)
.from(:users)
.where("active = true")
.where("created_at > '2024-01-01'")
.order(:created_at, :desc)
.limit(10)
.to_sql
Singleton (Using Module)
# Ruby-idiomatic singleton using module
module Configuration
class << self
attr_accessor :api_key, :environment
def configure
yield self
end
def production?
environment == :production
end
end
end
Configuration.configure do |config|
config.api_key = "secret"
config.environment = :production
end
Configuration.api_key # => "secret"
Structural Patterns
Decorator (Using Modules)
class Coffee
def cost = 2.0
def description = "Coffee"
end
module Milk
def cost = super + 0.5
def description = "#{super} with milk"
end
module Sugar
def cost = super + 0.25
def description = "#{super} with sugar"
end
module Whip
def cost = super + 0.75
def description = "#{super} with whip"
end
coffee = Coffee.new
coffee.extend(Milk)
coffee.extend(Sugar)
coffee.extend(Whip)
coffee.description # => "Coffee with milk with sugar with whip"
coffee.cost # => 3.5
Adapter
# Existing interface
class OldPrinter
def print_document(text)
puts "Printing: #{text}"
end
end
# New interface we want to use
class ModernPrinter
def render(document)
puts "Rendering: #{document.content}"
end
end
# Adapter
class PrinterAdapter
def initialize(modern_printer)
@printer = modern_printer
end
def print_document(text)
document = OpenStruct.new(content: text)
@printer.render(document)
end
end
# Usage
printer = PrinterAdapter.new(ModernPrinter.new)
printer.print_document("Hello") # Works with old interface
Facade
class OrderFacade
def initialize(user)
@user = user
@cart = ShoppingCart.new(user)
@inventory = InventoryService.new
@payment = PaymentService.new
@shipping = ShippingService.new
end
def place_order(payment_details)
items = @cart.items
# Complex subsystem interactions hidden
@inventory.reserve(items)
@payment.charge(@user, @cart.total, payment_details)
order = Order.create(user: @user, items: items)
@shipping.schedule(order)
@cart.clear
order
rescue PaymentError => e
@inventory.release(items)
raise
end
end
# Simple interface for clients
facade = OrderFacade.new(current_user)
order = facade.place_order(credit_card_info)
Behavioral Patterns
Strategy (Using Blocks/Procs)
class Sorter
def initialize(&strategy)
@strategy = strategy || ->(a, b) { a <=> b }
end
def sort(items)
items.sort(&@strategy)
end
end
# Different strategies
by_name = Sorter.new { |a, b| a.name <=> b.name }
by_price = Sorter.new { |a, b| a.price <=> b.price }
by_date_desc = Sorter.new { |a, b| b.date <=> a.date }
by_name.sort(products)
by_price.sort(products)
Observer
module Observable
def add_observer(observer)
observers << observer
end
def remove_observer(observer)
observers.delete(observer)
end
def notify_observers(event, data = nil)
observers.each { |o| o.update(event, data) }
end
private
def observers
@observers ||= []
end
end
class Order
include Observable
attr_reader :status
def complete!
@status = :completed
notify_observers(:order_completed, self)
end
end
class EmailNotifier
def update(event, order)
case event
when :order_completed
send_confirmation_email(order)
end
end
end
class InventoryTracker
def update(event, order)
case event
when :order_completed
reduce_stock(order.items)
end
end
end
order = Order.new
order.add_observer(EmailNotifier.new)
order.add_observer(InventoryTracker.new)
order.complete!
Command
class Command
def execute
raise NotImplementedError
end
def undo
raise NotImplementedError
end
end
class AddItemCommand < Command
def initialize(cart, item)
@cart = cart
@item = item
end
def execute
@cart.add(@item)
end
def undo
@cart.remove(@item)
end
end
class CommandHistory
def initialize
@history = []
end
def execute(command)
command.execute
@history.push(command)
end
def undo
command = @history.pop
command&.undo
end
end
history = CommandHistory.new
history.execute(AddItemCommand.new(cart, item1))
history.execute(AddItemCommand.new(cart, item2))
history.undo # Removes item2
Additional Resources
Reference Files
references/pattern-examples.md- Extended examples and anti-patterns