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

RoleIn this exampleResponsibility
FacadeShopKnows which objects to consult, and in what order. Gives the UI one simple method.
Subsystem classesWallet, PriceList, GridDo the real work. They don't know the facade exists.
ClientCaller ofAsks for one thing instead of coordinating three objects.

Ruby implementation

facade.rb
# 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
end

In 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.new keep 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 Wallet directly.
  • Name it as a thing. Shop is an object with a place in the game, not a TowerPurchaseProcessor procedure 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.