Design patterns

Design patterns are a way to structure code so it becomes more modular, easily extendable and modifiable.

Behavioral

Strategy

Strategy is a composition based pattern for defining object behavior by giving it strategy object that handles encapsulated functionality.

When to use:

An object has different ways to perform the same method. When an object has methods with many if-else clauses, it is a symptom that class contains several different behaviors that could be handled by strategies.

Example:

A sing method that has several logical ways to perform (and is harder to test or extend):

class Bird
  def initialize(type)
    @type = type
  end

  def sing
    if type == :duck
      puts 'Quack'
    else
      puts 'I thought I taw a putty tat'
    end
  end
end

can be refactored to:

class QuackStrategy
  def sing
    puts 'Quack'
  end
end

class TweetStrategy
  def sing
    puts 'I thought I taw a putty tat'
  end
end

class Bird
  def initialize(sing_strategy)
    @sing_strategy = strategy
  end

  def sing
    @sing_strategy.sing
  end
end
>> Bird.new(QuackStrategy.new).sing
=> "Quack"

>> Bird.new(TweetStrategy.new).sing
=> "I thought I taw a putty tat"

Tips:

  • Reek’s ControlParameter smell is an indicator that code could be refactored to use strategies.

Template method

Design pattern based on inheritance, use case similar to one of a strategy pattern.

When to use:

Prefer using the strategy pattern to template method pattern whenever possible. Only use template method pattern in cases where strategy pattern does not fit.

Example:

class Bird
  def inspect
    puts "This is a #{self.class.to_s}"
    puts "It says #{sing}"
  end

  def sing
    raise NotImplementedError
  end
end

class Duck < Bird
  def sing
    puts 'Quack'
  end
end

class Blackbird < Bird
  def sing
    puts 'Chirp'
  end
end

Iterator

Iterator defines the way how an object should be traversed to access its contained objects. It can be a collection or a composite, or anything else that has a structure.

When to use:

There is a collection object that needs to be traversed in a custom way. Or there’s a complex object that needs traversing.

Example:

class NumberIterator
  def initialize(numbers = [])
    @numbers = numbers
  end

  def each_odd_number(&block)
    @numbers.each do |num|
      yield num if num % 2 != 0
    end
  end
end
>> NumberIterator.new([1, 2, 3, 4, 5]).each_odd_number { |num| puts num }
=> 1
=> 3
=> 5

Tips:

  • Combine iterators with ruby Enumerable module to get useful enumeration methods.

Observer

In observer pattern, object maintains a list of observers and notifies them when various events happen.

When to use:

When one or many objects depend on a state of the observable object and needs to know when observable object state changes. A preferred alternative to callbacks.

Example:

class User
  def initialize(name:, email:)
    @name = name
    @email = email
  end

  def email=(value)
    @email = value
    send_email_confirmation
  end

  private

  def send_email_confirmation
    puts "Sending email confirmation to #{@email}"
  end
end

can be refactored to:

class User
  def initialize(name:, email:, email_observers: [])
    @name = name
    @email = email
    @email_observers = email_observers
  end

  def email=(value)
    @email = value
    notify_email_observers
  end

  private

  def notify_email_observers
    @email_observers.each { |observer| observer.notify(@email) }
  end
end

class UserEmailObserver
  def notify(email)
    send_email_confirmation(email)
  end

  private

  def send_email_confirmation(email)
    puts "Sending email confirmation to #{email}"
  end
end
>> user = User.new(name: 'John', email: '[email protected]', email_observers: [UserEmailObserver.new])
>> user.email = '[email protected]'
=> "Sending email confirmation to [email protected]"

Command

A design pattern that decouples the object that invokes the operation from the object that knows how to perform it.

When to use:

  • Application works with operations that may be performed in any order (e. g., cut, paste, add, divide…).
  • Operations may need to be persisted and/or processed later.
  • Operations may need to be processed asynchronously.
  • Application has to support redo/undo of operations.

Example:

The following example implements a restaurant order system. The waiter (Invoker) takes an order (Command) from a customer by writing it down on the pad. The order is delegated to the chef (Receiver), which knows exactly how to make it.

class Chef
  def initialize(name)
    @name = name
  end

  def make_caesar_salad
    puts 'making Caesar salad'
  end

  def make_margherita_pizza
    puts 'making Margherita pizza'
  end
end

class Order
  def initialize(chef, dish)
    @chef = chef
    @dish = dish
  end

  def execute
    @chef.send("make_#{@dish}")
  end
end

class Waiter
  def initialize(name)
    @name = name
    @pad = []
  end

  def place_order(order)
    @pad << order
    order.execute
  end
end
>> chef = Chef.new('John')
>> waiter = Waiter.new('Joan')

>> waiter.place_order(Order.new(chef, :margherita_pizza))
=> "making Margherita pizza"
>> waiter.place_order(Order.new(chef, :caesar_salad))
=> "making Caesar salad"

Tips:

  • Composite can help implement this pattern with macro commands.
  • The invoker of the command should not now about the concrete command classes. It should use only the abstract interface.
  • Command pattern should not be used if the command is only a link between the receiver and the actions that carry out the request.
  • Command pattern should also be avoided if the command implements everything itself and does not send anything to the receiver (only the receiver should know how to perform the operation).

Chain of responsibility

The chain of responsibility pattern creates a chain of receiver objects for a request. This pattern decouples sender and receiver of request based on type of request. In this pattern each receiver contains a reference to another receiver.

When to use:

  • When the request can be processed by different handlers

Example:

class Project
  attr_accessor :code, :design

  def completed?
    code && design
  end
end

class Handler
  attr_accessor :successor

  def initialize(successor = nil)
    self.successor = successor
  end

  def handle(request)
    perform_actions(request)

    return true if request.completed?

    successor ? successor.handle(request) : false
  end
end

class Designer < Handler
  def perform_actions(request)
    request.design = 'Project design'
  end
end

class Developer < Handler
  def perform_actions(request)
    request.code = 'Project code'
  end
end

# Usage
designer = Designer.new
developer = Developer.new

designer.successor = developer

designer.handle(Project.new)

Structural

Decorator

Decorator accepts an object in its initializer and produces a decorated object. Decorated object follows interface of the original one, delegates some methods to the original object and modifies others when needed. This allows to change object behavior or add new features without changing behavior for objects of same class.

When to use:

  • We want to modify/add features to class, however, we are sure introduced features are not said class responsibility.
  • There is a need for models with more functionality than data storing, validation and association logic.

Example:

Let’s say we have a Comment model and want to post a comment to a Facebook’s wall after saving it locally. Instead of adding an after_save callback to Comment model, we create a decorator that does posting to Facebook.

class Comment < ActiveRecord::Base
end

class CommentNotifier
  def initialize(comment)
    @comment = comment
  end

  def save
    @comment.save && notify_receiver
  end

  private

  def notify_receiver
    puts 'Notifying comment receiver about the new comment'
  end
end

Using decorated objects is pretty straight-forward:

class CommentsController < ApplicationController
  def create
    @comment = CommentNotifier.new(Comment.new(params[:comment]))

    if @comment.save
      flash[:notice] = 'Your comment was posted.'
      redirect_to comments_path
    else
      render 'new'
    end
  end
end

Tips:

  • Use ruby’s SimpleDelegator to avoid writing method delegations to original object:
class User
  def first_name
    'John'
  end

  def last_name
    'Doe'
  end

  def full_name
    "#{first_name} #{last_name}"
  end
end

class UserDecorator < SimpleDelegator
  def full_name
    "#{last_name}, #{first_name}"
  end
end
>> decorated_user = UserDecorator.new(User.new)
>> decorated_user.full_name
=> "Doe, John"

Proxy

Very similar design pattern to Decorator pattern. While primary intent of decorators is adding and extending the functionality of objects, proxies are used to modify a behavior of existing object methods. Another difference is that decorator always requires accepting decorated object in its initializer. Proxy, on the other hand, can create proxied object when needed.

When to use:

  • Building object is expensive, and we want to delay it as much as possible.
  • Limit access to the object (e.g. authorization).

Example:

class Message
  attr_reader :owner, :body

  def initialize(owner:, body:)
    @owner = owner
    @body = body
  end
end

class MessageReader
  attr_reader :user, :message

  def initialize(user:, message:)
    @user = user
    @message = message
  end

  def read
    message.body
  end
end

class MessageReaderProxy
  def initialize(user:, message:)
    @reader = MessageReader.new(user: user, message: message)
  end

  def read
    raise 'Unauthorized' unless @reader.user == @reader.message.owner
    @reader.read
  end
end
>> message = Message.new(owner: 'Charles', body: 'Hello, world!')
>> reader = MessageReader.new(user: 'Charles', message: message)
>> reader.read
=> "Hello, world!"

>> proxy = MessageReaderProxy.new(user: 'Charles', message: message)
>> proxy.read
=> "Hello, world!"

>> proxy = MessageReaderProxy.new(user: 'Alice', message: message)
>> proxy.read
=> RuntimeError: Unauthorized

Adapter

Code using adapter pattern is structured in a way that adapter objects provide a single interface to access methods to different underlying objects.

When to use:

When you want to produce the unified interface for objects of unrelated classes.

Example:

Say we want to post a message on a Facebook’s wall and to Twitter’s feed at the same time. We have two classes that do post to respective services:

class FacebookPoster
  def self.post_message(message)
    puts "Posts '#{message}' to FB wall"
  end
end

class TwitterMessage
  def initialize(msg)
    @msg = msg
  end

  def send
    puts "Sending '#{@msg}' to Twitter"
  end
end

We can use adapters to provide a unified interface for message posting:

module CommentAdapters
  module Facebook
    def self.post(message)
      FacebookPoster.post_message(message)
    end
  end

  module Twitter
    def self.post(message)
      TwitterMessage.new(message).send
    end
  end
end

class Comment
  ADAPTERS = [
    CommentAdapters::Facebook,
    CommentAdapters::Twitter
  ]

  def initialize(message)
    @message = message
  end

  def post_everywhere
    ADAPTERS.each do |adapter|
      adapter.post(@message)
    end
  end
end
>> Comment.new('Hello, world!').post_everywhere
=> "Posts 'Hello, world!' to FB wall"
=> "Sending 'Hello, world!' to Twitter"

Tips:

  • You can pass adapter as a symbol parameter with a little of Ruby magic:
class Comment
  def initialize(message)
    @message = message
  end

  def post_to(adapter)
    CommentAdapters.const_get(adapter.to_s.capitalize).post(@message)
  end
end
>> Comment.new('Hello, world!').post_to(:facebook)
=> "Posts 'Hello, world!' to FB wall"

>> Comment.new('Hello, world!').post_to(:twitter)
=> "Sending 'Hello, world!' to Twitter"

Facade

Provides simplified interface to consume an object.

When to use:

When you commonly access the object in a somewhat complicated way.

Example:

require 'net/http'

class NetworkingFacade
  def initialize(host)
    @uri = URI.parse(host)
  end

  def simple_get(page)
    Net::HTTP.start(@uri.host, @uri.port) { |http| http.get(page) }.body
  rescue SocketError
    nil
  end
end
>> NetworkingFacade.new('http://www.necolt.com').simple_get('/')
=> "<!DOCTYPE html>..."

>> NetworkingFacade.new('http://www.necolt.commm').simple_get('/')
=> nil

Composite

A design pattern that allows having structures consisting of primitive objects and/or other structures and manipulate them uniformly (with a uniform interface).

When to use:

  • There is a need to manipulate a hierarchical collection which may consist of “simple” or “composite” objects. Composite objects may as well consist of simple and/or composite objects. Composite objects are processed in one way and primitive objects in another.
  • “The sum acts like one of the parts” situation.

Example:

This example implements elements of a tree-like structure: LeafNode and InternalNode. Both components have a uniform interface (method value) which is used to manipulate the structure (in this case, to get a string representation of a node):

class LeafNode
  def initialize(value)
    @value = value
  end

  def value
    "(#{@value})"
  end
end

class InternalNode
  def initialize(nodes:)
    @nodes = nodes
  end

  def value
    "[#{@nodes.map(&:value).join(', ')}]"
  end
end
>> node = InternalNode.new(
>>   nodes: [
>>     LeafNode.new(5),
>>     InternalNode.new(nodes: [LeafNode.new(3), LeafNode.new(2)])
>>   ]
>> )

>> node.value
=> "[(5), [(3), (2)]]"

Bridge

The bridge pattern can be simply considered as a double abstraction. It is extremely useful when you need to apply a Strategy on an object that has varied behavior in certain cases. It promotes code extensibility, as new strategies and variations can be added.

When to use:

Whenever you’re applying a Strategy and notice that you have some extra complicated logic lying around that varies in certain conditions.

Example:

class MoneyInput
  def execute
    # steps to input money
  end
end

class MoneyWithdrawal
  def execute
    # steps to withdraw money
  end
end

class BilledOperation
  def initialize(action)
    @action = action
  end

  def execute
    @action.execute
    bill_operation
    # some additional logic
  end

  def bill_operation
    # steps to bill the operation
  end
end

class Operation
  def initialize(action)
    @action = action
  end

  def execute
    @action.execute
    # some additional logic
  end
end

class ATM
  def initialize(billed)
    @billed = billed
  end

  def execute_action(action)
    operation.new(action).execute
  end

  private

  def operation
    @operation ||= begin
      if @billed
        BilledOperation
      else
        Operation
      end
    end
  end
end

Tips

  • It’s a great choice for building complicated internal APIs.
  • Make sure you actually need it, it shouldn’t be used for simple logic. As it increases the application complexity.

Flyweight

You can think of the flyweight pattern as a modification to a conventional object factory. The modification being that rather than just creating new objects all the time the factory should check to see if it has already created the requested object and return it instead of creating it again.

When to use:

  • When you want to reduce the number of objects created

Example:

class Factory
  def initialize
    self.items = {}
  end

  def item(item_type)
    items[item_type] ||= create_item(item_type)
  end

  private

  attr_accessor :items

  def create_item(item_type)
    # creates an item
  end
end

# Usage

factory = Factory.new

circle = factory.item(:circle)
square = factory.item(:square)

Creational

Singleton

A design pattern that allows a class to instantiate only one single object and global access is provided to that object. Since singleton in lazy instantiated (i.e. instance only created on first use, and the same instance used later on), it provides performance and memory usage benefits.

When to use:

  • A case when a class can only have a single instance.
  • There is a need for a global state (singletons are preferred to global variables).
  • Concurrent access to a shared resource (e.g. database connection).
  • When creating an instance of a class is very expensive, however, one instance is sufficient enough.

Example:

class ErrorLogger
  def initialize
    puts 'Initializing error logger'
    @stream = STDERR
  end

  def log(message)
    @stream.puts(message)
  end

  def self.instance
    @@instance ||= new
  end

  private_class_method :new
end
>> ErrorLogger.instance.log("error 1")
=> "Initializing error logger"
=> "error 1"

>> ErrorLogger.instance.log("error 2")
=> "error 2"

>> ErrorLogger.new.log("error 3")
=> NoMethodError: private method `new' called for ErrorLogger:Class
Tips:
  • Include ruby’s Singleton module into class to make it a singleton:
require 'singleton'

class ErrorLogger
  include Singleton

  def initialize
    puts 'Initializing error logger'
    @stream = STDERR
  end

  def log(message)
    @stream.puts(message)
  end
end

Builder

A builder is an interface for constructing a part of an object’s state.

When to use:

If creating an object requires some boilerplate code, and the object is created at least in two places in the code, it makes sense to move object creation code to a builder object. It helps to avoid code duplication and possible bugs. Alternatively, object creation code should be moved to a builder object if creating object requires information or resources that should not be contained within the composed class. It consists of a director that uses builders to build an object.

Example:

# Director
class Search
  def initialize(params)
    @params = params
    @query = User.all
  end

  def execute
    [NameQuery, ActiveUserQuery].each do |query_builder|
      @query = query_builder.new(@query).call(@params)
    end
  end
end

# Concrete builder
class NameQuery
  def initialize(query)
    @query = query
  end

  def call(params)
    @query.where('name ILIKE :name', name: "%#{params[:name]}%")
  end
end

# Concrete builder
class ActiveUserQuery
  def initialize(query)
    @query = query
  end

  def call(_params)
    @query.where('current_sign_at <= :time', time: 1.day.ago)
  end
end

Factory Method

This design pattern is like a Template Method for creating objects. Rather than using the new method to create a new object instance, it advocates the usage of factory method (static or not).

When to use:

  • When there is a hierarchy of objects and the class cannot anticipate the type of objects it is supposed to create.
  • When a class wants its subclasses to decide which type of an object to create.
  • When the newly created object is used through a common interface.

Example:

The example shows a simple document creation system. create_document is the factory method here. Method new_document implements all the needed logic for getting a new document but it lets the subclass MyApplication to handle document instance creation.

class Document
  attr_reader :name

  def initialize(name)
    @name = name
  end
end

class MyDocument < Document
  def open
    puts "Opening the document named #{name}"
  end
end

class Application
  def initialize
    @docs = []
  end

  def new_document(name)
    document = create_document(name)
    @docs << document

    document.open
  end

  def report_all_documents
    @docs.each { |doc| puts doc.name }
  end
end

class MyApplication < Application
  def create_document(name)
    MyDocument.new(name)
  end
end
>> application = MyApplication.new
>> application.new_document("HelloWorld.txt")
=> "Opening the document named HelloWorld.txt"
>> application.new_document("Test.html")
=> "Opening the document named Test.html"
>> application.report_all_documents
=> "HelloWorld.txt"
=> "Test.html"

Tips:

  • If the code must know about the concrete class of the newly created object, then it should be considered carefully whether to use this pattern or not since it might bring unnecessary complexity to the application.

Object pool

An object pool is a set of initialized objects that are taken from a pool when needed and put back after use. Using a pool of initialized objects avoids initialization and destruction operations.

When to use:

When object initialization and/or destruction is expensive, the object is only used for a (short) period of time, and objects of a specific class are initialized often.

Example:

class Item
  def initialize
    puts 'Performing expensive initialization'
  end
end

class ItemPool
  def initialize(pool_size)
    @pool_size = pool_size
    @items = @pool_size.times.map { Item.new }
  end

  def take
    @items.shift
  end

  def release(item)
    @items << item
  end
end

Tips:

  • Make sure object state is reset before returning it to pool.
  • Object pool only makes sense when object initialization/destruction is expensive. Otherwise, simply creating objects and letting garbage collector to clean them can be more performant than programmatically resetting object state. Object pool also is memory demanding.
  • An example of popular gem using object pool: connection_pool.

Web application specific patterns

Query Objects

Query objects encapsulate complex SQL queries.

Example:

Simplified example that produces a query to find active users:

class ActiveUsersQuery
  def initialize(relation)
    @relation = relation
  end

  def call
    relation.where(email_confirmed: true).where('current_sign_in_at > ?', 1.year.ago)
  end
end

Value Objects

Value objects contain some values, and comparison results between value objects depend on contained values instead of object references (e.g. String or Time objects). Extracting Value objects promotes dry code.

Example:

class PhoneNumber
  attr_reader :country_code, :number

  def initialize(country_code, number)
    @country_code = country_code
    @number = number
  end

  def ==(other)
    country_code == other.country_code && number == other.number
  end

  def to_s
    "#{country_code} #{number}"
  end
end

Tips:

  • Use Ruby’s Comparable to get <, <=, ==, >=, >, between? methods just by implementing <=> method.
  • Use them to separate the application logic from the data layer, e.g. if a User has a phone number column in his table, it doesn’t mean that the phone number related code should belong to the User model.

Policy Objects

Policy objects contain read operations and expose object policies (object state, it’s abilities or attributes) to the object consumer.

Example:

class SubscriptionPolicy
  def initialize(user)
    @user = user
  end

  def subscribed?
    expiration_date = @user.subscribed_until
    expiration_date.present? && expiration_date <= Date.current
  end
end

Service Objects (Interactors)

Service objects responsibility is to connect your exposed WEB API to your core application domain. They are named as verbs and not nouns, indicating an action that you’re requesting. Whether your action consists of multiple steps, or validations, it should end up here, preferably composing internal components.

When to use:

  • A non-trivial action needs to be performed.
  • Action requires interaction between two or more objects.
  • Action is not model responsibility.
  • Action can be performed in multiple ways (has several logical branches).

Example:

class CommentOnPost
  attr_reader :message, :post, :commenter, :comment

  def initialize(message:, post:, commenter:)
    @message = message
    @post = post
    @commenter = commenter
  end

  def call
    @comment = post.comments.new(body: message, owner: commenter)

    if commenter.can_post_on?(post)
      comment.save
    else
      comment.errors.add(:base, :unauthorized)
    end
  end

  def success?
    comment.errors.blank?
  end
end
class CommentsController < ApplicationController
  expose(:post) { Post.find(params[:post_id]) }

  def create
    action = CommentOnPost.new(message: params[:message], post: post, commenter: current_user)
    action.call

    if action.success?
      # Handle success case
      ...
    else
      # Handle errors
      ...
    end
  end

end

Tips:

class CommentOnPost
  include Interactor

  delegate :message, :post, :commenter, :comment, to: :context

  def call
    build_comment
    save_comment
  end

  private

  def build_comment
    context.comment = post.comments.new(body: message, owner: commenter)
  end

  def save_comment
    context.fail! unless commenter.can_post_on?(post)
    comment.save
  end
end

Form Objects

Form objects handle updating one or many ActiveRecord models. Using form objects bring a number of benefits:

  • Keeps ActiveRecord model clean.
  • Validation and persistence logic is isolated and easily testable.

When to use:

  • Attributes must be modified before persisted in the database (e.g. stripping, sanitizing or converting input data).
  • Additional steps must be taken after persisting data (e.g. sending an email after creating a new user). Better alternative to callbacks.
  • Case-specific validations needed (instead of adding conditional validations to ActiveRecord models).
  • Logic, that doesn’t belong to model persistence (e.g. search form - it doesn’t persist anything in the database, however, still is a form).

Example:

class OrderForm
  include ActiveModel::Validations

  attr_accessor :price, :shipping_address

  validates :price, numericality: { greater_than: 0 }
  validates :shipping_address, :presence, length: { minimum: 10, maximum: 200 }

  def initialize(params = {})
    self.price = params[:price]
    self.shipping_address = params[:shipping_address]
  end

  def save
    return false unless valid?
    order.save
  end

  private

  def order
    Order.new(price: price, shipping_address: shipping_address)
  end
end
>> order = OrderForm.new(price: 0.0)
>> order.save
=> false
>> order.errors.full_messages.to_sentence
=> "Price must be greater than 0 and Shipping address is too short (minimum is 10 characters)"

>> order = OrderForm.new(price: 100.0, shipping_address: 'Planet Earth, Solar System, Milky Way')
>> order.save
=> true

Tips:

  • If logic is too complex for one form, use Service Objects / Interactors.

View objects

View objects contain logic that is needed only for displaying information and don’t belong to models.

Example:

class PostViewObject
  attr_reader :post

  delegate :title, :name, :user, to: :post

  def initialize(post)
    @post = post
  end

  def title_with_author
    "#{title} by #{user.name} @ #{created_at}"
  end
end