Proxy

Provide a surrogate or placeholder for another object to control access to it.

Also known as: Surrogate

Participants

RoleIn this exampleResponsibility
Subject#kinds, #sprite(kind)The interface the proxy and the real object share.
Real SubjectSpriteSheetThe expensive object doing the real work.
ProxyLazySpriteSheetLooks like a SpriteSheet, creates the real one on first use, and forwards to it.

Ruby implementation

proxy.rb
# The Real Subject: expensive to create, because it loads every sprite.
class SpriteSheet
  attr_reader :name

  def initialize(name, loader)
    @name = name
    @sprites = loader.call(name)
  end

  def kinds = @sprites.keys
  def sprite(kind) = @sprites.fetch(kind)
end

# The Virtual Proxy: stands in for a SpriteSheet, and only loads the real
# one the first time a message actually needs it.
class LazySpriteSheet
  def initialize(name, loader)
    @name = name
    @loader = loader
  end

  def name = @name
  def loaded? = !@sheet.nil?

  def method_missing(message, ...)
    return super unless sheet.respond_to?(message)

    sheet.public_send(message, ...)
  end

  def respond_to_missing?(message, include_private = false)
    sheet.respond_to?(message, include_private)
  end

  private

  def sheet
    @sheet ||= SpriteSheet.new(@name, @loader)
  end
end

In use

loader = ->(name) { puts "loading #{name}..."; { goblin: "g", troll: "T" } }

sheet = LazySpriteSheet.new("enemies", loader)
sheet.name            # => "enemies"   (nothing loaded yet)
sheet.sprite(:goblin) # prints "loading enemies..." => "g"
sheet.sprite(:troll)  # => "T"   (already loaded)

Notes

Ruby idioms

  • method_missing + respond_to_missing? makes a proxy that forwards anything the real subject understands. Always define both, so respond_to? stays accurate.
  • (message, ...) forwards positional, keyword and block arguments in one go (Ruby 2.7+).
  • Answer what you can without loading. #name is defined on the proxy, so cheap questions never pay the loading cost.
  • Active Record associations are proxies: post.comments doesn’t run a query until you use it.

Kinds of proxy

  • Virtual: delays expensive creation, like this sprite sheet, or loading the curses library only when the game first draws.
  • Protection: checks permissions before forwarding.
  • Remote: stands in for an object in another process (DRb).
  • Caching: remembers results of expensive calls.

Watch for

  • Proxies that rely on method_missing are slower and harder to search. If the interface is small, define the forwarding methods explicitly with Forwardable.

The proxy has the same interface as the real object, so clients don’t need to know which one they hold.