Proxy
Provide a surrogate or placeholder for another object to control access to it.
Also known as: Surrogate
Participants
| Role | In this example | Responsibility |
|---|---|---|
| Subject | #kinds, #sprite(kind) | The interface the proxy and the real object share. |
| Real Subject | SpriteSheet | The expensive object doing the real work. |
| Proxy | LazySpriteSheet | Looks like a SpriteSheet, creates the real one on first use, and forwards to it. |
Ruby implementation
# 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
endIn 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, sorespond_to?stays accurate.(message, ...)forwards positional, keyword and block arguments in one go (Ruby 2.7+).- Answer what you can without loading.
#nameis defined on the proxy, so cheap questions never pay the loading cost. - Active Record associations are proxies:
post.commentsdoesn’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_missingare slower and harder to search. If the interface is small, define the forwarding methods explicitly withForwardable.
The proxy has the same interface as the real object, so clients don’t need to know which one they hold.