双十一刚过, 小正收到了眼花缭乱的衣服和化妆品. 衣服作为装饰物, 按照顺序依次对她进行装饰.

欲表达一种需求: 先穿白色T恤, 再穿黑色短裙, 最后展示看效果.

让用户接口呈如下的形式:

wear TShirt, '白色'
wear Skirt, '黑色'

look Beauty.new('领导')

其实思路很简单, 我们维护一个栈, 每次 wear 的时候, 把装饰物记录到栈里. 等所有的 wear 完成了, 衣服就都准备好了, 等到要展示效果的时候, 就把他们一件一件展示出来.

这里的关键特性是: 给 Beauty 穿衣服的时候, 不需要修改 Beauty .

class Beauty
  attr_reader :name

  def initialize(name)
    @name = name
  end

  def call(env)
    env
  end
end

class Clothes
  attr_reader :beauty, :color

  def initialize(beauty, color: 'colorless')
    @beauty = beauty
    @color = color
  end

  def call(env = [])
    env << { type: self.class, color: color }
    beauty.call(env)
  end
end

class TShirt < Clothes
end
class Skirt < Clothes
end

module Wear
  def wear(clothes_klass, *args, &block)
    wearables << proc { |beauty| clothes_klass.new(beauty, *args, &block) }
  end

  def look(beauty)
    beauth_with_clothes = beauty
    @wearables.reverse.each do |wearable|
      beauth_with_clothes = wearable.call(beauth_with_clothes)
    end
    # same as:
    # beauth_with_clothes = @wearables.reverse.inject(beauty) { |beauth_with_clothes, wearable| wearable[beauth_with_clothes] }

    env_result = beauth_with_clothes.call
    puts describe(beauty, env_result)
  end

  private

  def wearables
    @wearables ||= []
  end

  def describe(beauty, env)
    "#{beauty.name}今天穿的是: " + \
      env.map { |item| "#{item[:color]}#{item[:type]}" }.join(', ') + \
      '.'
  end
end
class Show
  extend Wear

  wear TShirt, color: '黑色'
  wear Skirt, color: '紫色'
  look Beauty.new('小正')
end

Show.new