
June 28, 2026
In the first article of this series, we explored why Ruby changes the way developers think about design patterns. Features like duck typing, modules, blocks, and delegation often replace the ceremony required in more rigid object-oriented languages.
Now it’s time to put those ideas into practice.
In this article we’ll explore six classic Gang of Four patterns and see how they evolve when implemented the Ruby way.
Factory Pattern
The Problem
Sometimes object creation becomes more complex than simply calling Class.new.
Perhaps the application needs different implementations depending on configuration, environment, or user input.
Instead of scattering conditional logic throughout the application, we centralize object creation.
Traditional OO Approach
In Java or C++, factories frequently involve interfaces, abstract factories, and multiple concrete implementations.
Ruby usually doesn’t need that much structure.
class PaymentFactory def self.build(type) case type when :paypal PaypalPayment.new when :stripe StripePayment.new else raise ArgumentError, "Unknown payment provider" end endend
Usage:
payment = PaymentFactory.build(:stripe)payment.pay(100)
Simple.
Readable.
No unnecessary abstraction.
Builder Pattern
The Problem
Some objects require many optional attributes.
Constructors quickly become difficult to read.
Instead of creating constructors with ten parameters, builders allow objects to be assembled step by step.
class Report attr_accessor :title, :author, :include_graphsendreport = Report.new.tap do |r| r.title = "Monthly Sales" r.author = "Alice" r.include_graphs = trueend
Ruby’s tap method often replaces an entire Builder hierarchy.
For more complex scenarios, a dedicated builder object can still improve readability, but Ruby encourages simpler solutions first.
Adapter Pattern
The Problem
Applications frequently integrate third-party APIs.
Unfortunately, every service exposes a different interface.
Instead of changing the rest of the application, we adapt external APIs to our own.
class StripeAdapter def initialize(client) @client = client end def pay(amount) @client.create_payment(amount) endend
Now every payment provider simply responds to:
payment.pay(amount)
The rest of the application doesn’t care which provider is being used.
This pattern is extremely common in production Ruby applications.
Decorator Pattern
The Problem
Adding functionality through inheritance eventually becomes unmanageable.
Decorators wrap an object instead.
Ruby’s standard library already provides a great solution:
require "delegate"class LoggingService < SimpleDelegator def call(...) puts "Starting..." result = super puts "Finished." result endend
Instead of manually forwarding every method, SimpleDelegator handles delegation automatically.
It’s one of Ruby’s most underrated standard library classes.
Proxy Pattern
The Problem
Sometimes an object should control access to another object.
Examples include:
- Lazy loading
- Authorization
- Remote services
- Caching
class CachedWeather def initialize(api) @api = api @cache = {} end def forecast(city) @cache[city] ||= @api.forecast(city) endend
Clients interact with the proxy exactly as they would with the real object.
The proxy simply adds behavior behind the scenes.
Facade Pattern
The Problem
Complex subsystems expose dozens of classes.
Most clients only need one simple entry point.
A Facade provides that simplified interface.
class Checkout def complete(order) Inventory.reserve(order) Payment.process(order) Shipping.schedule(order) Notification.send(order) endend
Instead of coordinating four separate services, the application calls:
Checkout.new.complete(order)
The implementation stays organized while the public API remains clean.
Ruby’s Secret Weapon: Composition
One theme appears repeatedly throughout these patterns.
Ruby favors composition over inheritance.
Instead of creating deep class hierarchies, Ruby developers combine small objects that each have a single responsibility.
Modules.
Delegation.
Duck typing.
Small objects.
Together they produce systems that are easier to test, extend, and understand.
That’s why many classic GoF implementations feel surprisingly small in Ruby.
The patterns haven’t disappeared.
The language simply removes much of the boilerplate.
Looking Ahead
We’ve now explored the most common creational and structural patterns using idiomatic Ruby.
In the final article, we’ll move to behavioral patterns—including Strategy, Command, Observer, and State—and examine how Rails itself embraces these ideas through Service Objects, Query Objects, Policies, Presenters, and other conventions that have become staples of modern Ruby development.
Understanding design patterns isn’t about memorizing diagrams.
It’s about recognizing problems—and knowing how Ruby lets you solve them with elegance instead of complexity.
