Design Patterns, the Ruby Way (Part 2): Modern Creational and Structural Patterns

Design Patterns, the Ruby Way (Part 2): Modern Creational and Structural Patterns
Design Patterns, the Ruby Way (Part 2): Modern Creational and Structural Patterns

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.


Tokyo Topographic Map
Built for Ruby on Rails

Build Maps Without
Google APIs

Generate beautiful production-ready maps directly from your Rails backend. Fast rendering, zero external dependencies, full control.

✓ No API fees ✓ Self-hosted ✓ Rails Native ✓ Fast Rendering
Why developers switch
Replace expensive map stacks.

Stop relying on third-party map billing and bloated JS libraries. Render static or dynamic maps directly in Ruby.

Try It Now
Tokyo MapView Demo

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
end
end

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_graphs
end
report = Report.new.tap do |r|
r.title = "Monthly Sales"
r.author = "Alice"
r.include_graphs = true
end

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)
end
end

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
end
end

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)
end
end

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)
end
end

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.

Article content

Leave a comment