酒と泪とRubyとRailsと

Ruby on Rails と Objective-C は酒の肴です!

ファクトリメソッド Ruby 2.0.0 デザインパターン速攻習得[Factory Method]

GoFのデザインパターン(Design Pattern)の一つ、ファクトリメソッド(Factory Method)をRubyのサンプルコードで紹介します。

ファクトリメソッドは、インスタンスの生成をサブクラスに任せるパターンです。インスタンスの生成部分を切り離すことで、結合度を下げて追加・変更・保守を容易にします。


ソースコードを使ったファクトリメソッドの説明

ファクトリメソッドをソースコードを使って説明します。
ここではサックスとサックスを作る工場を例に考えます。

サックスを表すSaxophoneクラスは、音を鳴らす(play)メソッドを持っている
楽器工場を表すInstrumentFactoryクラスは
  * コンストラクタの引数で楽器の数を受け取る
  * 楽器を出荷するメソッド「ship_out」を持つ

を満たすコードを書いていきます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# サックス (Product)
class Saxophone
  def initialize(name)
    @name = name
  end

  def play
    puts "#{@name} は音を奏でています"
  end
end

# 楽器工場 (Creator)
class InstrumentFactory
  def initialize(number_saxophones)
    @saxophones = []
    number_saxophones.times do |i|
      saxophone = Saxophone.new("サックス #{i}")
      @saxophones << saxophone
    end
  end

  # 楽器を出荷する
  def ship_out
    @tmp = @saxophones.dup
    @saxophones = []
    @tmp
  end
end

上のプログラムを呼び出してみます。

1
2
3
factory = InstrumentFactory.new(3)
saxophones = factory.ship_out
saxophones.each { |saxophone| saxophone.play }

この結果は次のようになります。

1
2
3
#=> サックス 0 は音を奏でています
#=> サックス 1 は音を奏でています
#=> サックス 2 は音を奏でています

ここで、トランペット(Trumpet)のモデルを追加することになりました。 インターフェイスはサックスと全く同じものとします。

1
2
3
4
5
6
7
8
9
10
# トランペット (Product)
class Trumpet
  def initialize(name)
    @name = name
  end

  def play
    puts "トランペット #{@name} は音を奏でています"
  end
end

先ほど作ったInstrumentFactoryモデルをもう一度確認してみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 楽器工場 (Creator)
class InstrumentFactory
  def initialize(number_saxophones)
    @saxophones = []
    number_saxophones.times do |i|
      saxophone = Saxophone.new("サックス #{i}")
      @saxophones << saxophone
    end
  end

  # 楽器を出荷する
  def ship_out
    tmp = @saxophones.dup
    @saxophones = []
    tmp
  end
end

トランペットを追加する場合にInstrumentFactoryモデルで問題になるのは、コンストラクタ(initialize)でサックスを作っている点です。

1
saxophone = Saxophone.new("サックス #{i}")

そこで、InstrumentFacotory内でサックスを生成している部分をサブクラス(SaxophoneFacotory)に切り出します。また、トランペットを生成するTrumpetFactoryクラスを作成します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 楽器工場 (Creator)
class InstrumentFactory
  def initialize(number_instruments)
    @instruments = []
    number_instruments.times do |i|
      instrument = new_instrument("楽器 #{i}")
      @instruments << instrument
    end
  end

  # 楽器を出荷する
  def ship_out
    @tmp = @instruments.dup
    @instruments = []
    @tmp
  end
end

# SaxophoneFactory: サックスを生成する (ConcreteCreator)
class SaxophoneFactory < InstrumentFactory
  def new_instrument(name)
    Saxophone.new(name)
  end
end

# TrumpetFactory: トランペットを生成する (ConcreteCreator)
class TrumpetFactory < InstrumentFactory
  def new_instrument(name)
    Trumpet.new(name)
  end
end

InstrumentFactoryはnew_instrument(楽器の生成)の処理を抽象化しています。
(抽象化は、言い換えると「異なるものをひとまとめにする」ということです)

上のプログラムの結果を確認します。

1
2
3
4
5
6
7
8
9
10
11
12
factory = SaxophoneFactory.new(3)
saxophones = factory.ship_out
saxophones.each { |saxophone| saxophone.play }
#=> サックス 楽器 0 は音を奏でています
#=> サックス 楽器 1 は音を奏でています
#=> サックス 楽器 2 は音を奏でています

factory = TrumpetFactory.new(2)
trumpets = factory.ship_out
trumpets.each { |trumpet| trumpet.play }
#=> トランペット 楽器 0 は音を奏でています
#=> トランペット 楽器 1 は音を奏でています

上の通り、サックス/トランペットを作成、音を奏でる事ができました。

この例のようにクラスの選択をサブクラスに任せることを「FactoryMethod」と呼びます。
ファクトリメソッドは次の3つで構成されています。

Creator: ConcreteFactoryの共通部分の処理を行う(この例ではInstrumentFactory)
ConcreteCreator: 実際にオブジェクトの生成を行う(この例ではSaxophoneFactory, TrumpetFactoryクラス)
Product: ConcreteFactoryによって生成される側のオブジェクト(この例では、Saxophone,Trumpetクラス)

ファクトリメソッドは生成するProductへの依存度を下げて、生成部分を切り離すことで変更や追加、保守を容易にするメリットがあります。

このサンプルソースはGitHubにも置いているので良かったら使って下さい。

サンプルソース(GitHub)

Special Thanks

@ponta_さんにファクトリメソッドについて沢山アドバイスをいただきました。本当にありがとうございました!

サルでもわかる 逆引きデザインパターン 第2章 逆引きカタログ ロジック編 Factory/Factory Method(ファクトリ/ファクトリメソッド)

Factory - Murayama Blog.

RubyでTemplateメソッドパターンとFactoryメソッドパターン

Amazon.co.jp: Rubyによるデザインパターン: Russ Olsen, ラス・オルセン, 小林 健一, 菅野 裕, 吉野 雅人, 山岸 夢人, 小島 努: 本

変更来歴

12/10 09:00 GitHubへのサンプルソースの設置。導入文の修正
12/10 22:10 ファクトリの間違いを修正、Factory MethodとAbstract Factoryに分割
12/11 00:00 書籍へのリンクをAmazon アフィリエイトに変更
06/21 18:50 Ruby2.0.0対応、読みづらい部分を修正
08/09 22:15 例題コードの説明が不適切だったため、書き直し

おすすめの書籍