Factory Method パターン(特異クラス編)

http://d.hatena.ne.jp/rubyo/20060405/1144211265より。

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

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名前空間と同じようなものだと思っていましたが、ちょっと違うみたいです。スコープのメカニズムか何かが入っているのでしょうか。

IDCard::Factoryとするのであれば、IDCardのクラスの定義の中にFactoryクラスの定義を書きましょう。エラーの内容はFactory::IDCardと書いてあったりIDCard::Factoryと書いてあったりするのでよく分かりませんが推測すると、Factory::IDCardと定義しているのであれば、Factory::IDCard内でトップレベルのIDCardクラスを呼び出すのなら::IDCard.newとトップレベルであることを明示的に指定する必要があります。

詳しくは、http://www.ruby-lang.org/ja/man/index.cgi?cmd=view;name=%A5%AF%A5%E9%A5%B9%A1%BF%A5%E1%A5%BD%A5%C3%A5%C9%A4%CE%C4%EA%B5%C1を参考に。

ただこの場合は、入れ子になったクラスを作成するよりはIDCardFactoryクラスを定義してやってそれにFactoryモジュールをMix-inする方が個人的にはRubyっぽいような気がします。

あるいはFactoryをモジュールではなくクラスとして定義して、必要なFactoryに対しては特異クラスを使うというやり方なんかもあります。IDCardFactoryのインスタンスをそんなにたくさん使うということもないですし、Factoryクラスにちょっとだけ変更を加えたクラスを作成したい場合には便利です。

class Factory
  def initialize
    instance_eval(yield) if block_given?
  end
  def create(owner)
    product = create_product owner
    register_product product
    
    product
  end
  
  def create_product(owner)
  end

  def register_product(product)
  end
end

class IDCard
  attr_reader :owner
  def initialize(owner)
    @owner = owner
  end

  def use
  end
end

idcard_factory = Factory.new { "@owners = Array.new" }
class << idcard_factory
  attr_reader :owners
  
  def create_product(owner)
    IDCard.new(owner)
  end               
                    
  def register_product(product)
    @owners.push(product.owner)
  end               
                    
  protected :create_product, :register_product
end                 
                    
products = [        
  idcard_factory.create("id:rubyco"),
  idcard_factory.create("id:rubyo"),
  idcard_factory.create("id:walf443")
]                   
                    
products.each do |product|
  product.use       
end                 
                    
idcard_factory.owners.each do |owner|
  puts owner 
end