Facade
Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.
Participants
| Role | In this example | Responsibility |
|---|---|---|
| Facade | Shop | Knows which objects to consult, and in what order. Gives the UI one simple method. |
| Subsystem classes | Wallet, PriceList, Grid | Do the real work. They don't know the facade exists. |
| Client | Caller of | Asks for one thing instead of coordinating three objects. |
Ruby implementation
# A subsystem of small objects, each with its own interface.
class Wallet
attr_reader :gold
def initialize(gold)
@gold = gold
end
def afford?(amount) = amount <= gold
def spend(amount)
@gold -= amount
end
end
class PriceList
PRICES = { arrow: 50, frost: 70, cannon: 90 }.freeze
def price(kind) = PRICES.fetch(kind)
end
class Grid
def initialize(path:)
@path = path
@towers = {}
end
def free?(cell) = !@path.include?(cell) && !@towers.key?(cell)
def place(kind, cell)
@towers[cell] = kind
end
def tower_at(cell) = @towers[cell]
end
# The Facade: one simple shop counter in front of the whole subsystem.
# The UI asks for a tower; the shop knows which objects to consult.
class Shop
def initialize(wallet:, grid:, prices: PriceList.new)
@wallet = wallet
@grid = grid
@prices = prices
end
def buy(kind, cell)
price = @prices.price(kind)
return "Can't build there" unless @grid.free?(cell)
return "Not enough gold (need #{price})" unless @wallet.afford?(price)
@wallet.spend(price)
@grid.place(kind, cell)
"Built #{kind} tower for #{price}g"
end
endIn use
wallet = Wallet.new(120)
grid = Grid.new(path: [[0, 1], [1, 1], [2, 1]])
shop = Shop.new(wallet:, grid:)
shop.buy(:arrow, [3, 2]) # => "Built arrow tower for 50g"
shop.buy(:arrow, [1, 1]) # => "Can't build there"
shop.buy(:cannon, [4, 2]) # => "Not enough gold (need 90)"
wallet.gold # => 70
Notes
Ruby idioms
- Inject the subsystem, with defaults where it helps. Keyword arguments like
prices: PriceList.newkeep the facade easy to use and easy to test with fakes. - The subsystem stays public. A facade is a convenience, not a wall. Code that needs finer control can still talk to
Walletdirectly. - Name it as a thing.
Shopis an object with a place in the game, not aTowerPurchaseProcessorprocedure wrapped in a class.
Reach for it when
- Clients repeatedly write the same sequence of calls across several objects.
- A UI reaches deep into many model classes. A facade gives it one small interface to depend on instead.
Watch for
- A facade that collects every operation in the subsystem becomes a god object. Keep it to the common paths.
A facade gives clients one simple way in, while the objects behind it stay available when they need finer control.