Factory Method パターン

id:hyuki さんのデザインパターン本から Factory Method パターンを移植してみます。

まずは素で移植したものです。

#!/usr/local/bin/ruby

class Factory
  def create(owner)
    p = self.create_product(owner)
    self.register_product(p)
    return p
  end
end

class IDCard
  attr_reader :owner
  def initialize(owner)
    puts "#{owner} のカードを作ります。"
    @owner = owner
  end

  def use
    puts "#{owner} のカードを使います。"
  end
end

class IDCardFactory < Factory
  attr_reader :owners
  
  def initialize
    @owners = Array.new
  end

  def create_product(owner)
    IDCard.new(owner)
  end

  def register_product(p)
    @owners.push(p.owner)
  end

  protected :create_product, :register_product
end

factory = IDCardFactory.new
products = [
  factory.create("るびお"),
  factory.create("るびこ"),
  factory.create("まっつさん")
]

products.each do |product|
  product.use;
end

factory.owners.each do |owner|
  puts owner
end

書籍では Factory クラスは抽象クラスになっていますが、ここでは普通のクラスとして実装しています。IDCardFactory はその Factory を継承します。

実行すると以下のようになります。

$ ruby factory_method.rb
るびお のカードを作ります。
るびこ のカードを作ります。
まっつさん のカードを作ります。
るびお のカードを使います。
るびこ のカードを使います。
まっつさん のカードを使います。
るびお
るびこ
まっつさん

たまたま目を通した Ruby のモジュールのマニュアルユーザーズガイド

モジュールはインスタンスを生成しない(抽象クラスであるこ とが保証される).

とあって、モジュールを抽象クラスとして利用することができることが分かりました。そこで Factory クラスをモジュールに変更しました。

#!/usr/local/bin/ruby

module Factory
  def create(owner)
    ...
  end
end

class IDCardFactory
  include Factory
  ...
end

Ruby っぽくなったし、無理矢理抽象クラスみたいなこともないのですっきりしました。抽象クラスを実現したい場合にはモジュールで、と覚えておきます。

ところで、IDCardFactory という名前が Ruby っぽくないかなと思ったので、IDCard::FactoryFactory::IDCard に変更してみたところ

factory_method.rb:33:in `initialize': wrong number of arguments (1 for 0) (ArgumentError)
        from factory_method.rb:33:in `create_product'
        from factory_method.rb:5:in `create'
        from factory_method.rb:43

とエラーになってしまいました。どうも、IDCard.new しているところで IDCard クラスではなく Factory::IDCard を対象にしてしまっているような感じです。Foo::Bar は Perl名前空間と同じようなものだと思っていましたが、ちょっと違うみたいです。スコープのメカニズムか何かが入っているのでしょうか。