酒と泪とRubyとRailsと

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

アブストラクトファクトリ Ruby 2.0.0 デザインパターン速攻習得[Abstract Factory][Design Pattern]

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

アブストラクトファクトリは、矛盾のないオブジェクトの生成を行うためのパターンです。


ソースコードを使ったAbstract Factoryの説明

Abstract Factoryをソースコードを使って説明します。
ここでは次のような池をサンプルとして取り上げます。

* 動物を表すクラス:
  * アヒルを表すDuckクラスは、食事(eat)メソッドを持っている
  * カエルを表すFrogクラスは、食事(eat)メソッドを持っている

* 植物を表すクラス:
  * 藻を表すAlgaeクラスは、成長(grow)メソッドを持っている
  * スイレンを表すWaterLilyクラスは、成長(grow)メソッドを持っている

池の生態系を生成するクラス:
  * コンストラクタで動物と植物を定義する
  * 動物、植物のオブジェクトを返すメソッドを持っている

* 池の環境(動物と植物の組み合わせ)は次の2種類のみが許されている
  * DuckとWaterLily
  * FrogとAlgae

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

まず、アヒル(Duckクラス)とカエル(Frogクラス)は次のようになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# アヒル
class Duck
  def initialize(name)
    @name = name
  end

  # 食べる(eat)
  def eat
    puts "アヒル #{@name} は食事中です"
  end
end

# カエル
class Frog
  def initialize(name)
    @name = name
  end

  # 食べる(eat)
  def eat
    puts "カエル #{@name} は食事中です"
  end
end

一方、藻(Algaeクラス)とスイレン(WaterLilyクラス)のは次のようになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 植物/藻
class Algae
  def initialize(name)
    @name = name
  end

  def grow
    puts "藻 #{@name} は成長中です"
  end
end

# 植物/スイレン
class WaterLily
  def initialize(name)
    @name = name
  end

  def grow
    puts "スイレン #{@name} は成長中です"
  end
end

次に池を作成する前に、「池の環境の制約」について考えます。

* 池の環境(動物と植物の組み合わせ)は2種類のみが許されている
  * アヒル(Duckクラス)とスイレン(WaterLilyクラス)
  * カエル(Frogクラス)と藻(Algaeクラス)

この池の環境の制約を守ること、言い換えると矛盾のないオブジェクトの組み合わせを作るのが「Abstract Factoryパターン」です。 今回はこの矛盾のない環境の作成を次の2つのクラスに担当してもらいます。

* カエル(Frog)と藻(Algae)の生成を行う => FrogAndAlgaeFactory
* アヒル(Duck)とスイレン(WaterLily)の生成を行う => DuckAndWaterLilyFactory

さらに上の2つのクラスのベースとなる池の生態系を表すクラスOrganismFactoryを作り、上記のクラスが継承するようにします。

ということで、ソースコードはこちら。

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# 池の生態系を作る (Abstract Factory)
class OrganismFactory
  def initialize(number_animals, number_plants)
    @animals = []
    # 池の動物を定義する
    number_animals.times do |i|
      animal = new_animal("動物 #{i}")
      @animals << animal
    end

    @plants = []
    # 池の植物を定義する
    number_plants.times do |i|
      plant = new_plant("植物 #{i}")
      @plants << plant
    end
  end

  # 植物についてのオブジェクトを返す
  def get_plants
    @plants
  end

  # 動物についてのオブジェクトを返す
  def get_animals
    @animals
  end
end

# カエル(Frog)と藻(Algae)の生成を行う (Concrete Factory)
class FrogAndAlgaeFactory < OrganismFactory
  private

  def new_animal(name)
    Frog.new(name)
  end

  def new_plant(name)
    Algae.new(name)
  end
end

# アヒル(Duck)とスイレン(WaterLily)の生成を行う(Concrete Factory)
class DuckAndWaterLilyFactory < OrganismFactory
  private

  def new_animal(name)
    Duck.new(name)
  end

  def new_plant(name)
    WaterLily.new(name)
  end
end

上のプログラムを実行した結果を載せておきます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
factory = FrogAndAlgaeFactory.new(4,1)
animals = factory.get_animals
animals.each { |animal| animal.eat }
#=> カエル 動物 0 は食事中です
#=> カエル 動物 1 は食事中です
#=> カエル 動物 2 は食事中です
#=> カエル 動物 3 は食事中です
plants = factory.get_plants
plants.each { |plant| plant.grow }
#=> 藻 植物 0 は成長中です

factory = DuckAndWaterLilyFactory.new(3,2)
animals = factory.get_animals
animals.each { |animal| animal.eat }
#=> アヒル 動物 0 は食事中です
#=> アヒル 動物 1 は食事中です
#=> アヒル 動物 2 は食事中です
plants = factory.get_plants
plants.each { |plant| plant.grow }
#=> スイレン 植物 0 は成長中です
#=> スイレン 植物 1 は成長中です

矛盾のない組み合わせて、オブジェクトを生成できた事がわかります。

このサンプルソースはGitHubにも置いています。

サンプルソース(GitHub)

Abstract Factoryの構成

Abstract Factoryは次の3つの要素で構成されています。

AbstractFactory: ConcreteFactoryの共通部分の処理を行う(この例ではPond)
ConcreteFactory: 実際にオブジェクトの生成を行う
(この例ではFrogAndAlgaeFactoryとDuckAndWaterLilyFactoryクラス)
Product: ConcreteFactoryによって生成される側のオブジェクト
(この例では、Duck,Frog, WaterLily, algaeクラス)

アブストラクトファクトリのメリットは?

* 関連し合うオブジェクトの集まりを生成することができる
* 整合性が必要となるオブジェクト群を誤りなしに生成できる

Special Thanks

@chinmoさんにアブストラクトファクトリについてコード付きのコメントを頂きました。深謝です!

ma2さんにブログ上に記述したコードがOrganismFactoryを継承していない部分のミスをご指摘頂きました。ミスすんませんでした&ma2さん、本当に有難うございます!

Factory - Murayama Blog.

デザインパターン-AbstractFactory

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

変更来歴

12/12/10 23:10 11回FactoryをFactoryMethodとAbstractFactoryに分割
12/12/11 00:00 書籍へのリンクをAmazon アフィリエイトに変更
13/06/21 19:10 Ruby2.0.0対応、読みづらい部分を修正
13/08/15 13:15 モデリング・説明が不適切だったため、修正
14/01/18 09:35 継承の記述が抜けていたため、修正
15/05/26 20:00 コメントの記述にミスがあったので修正

おすすめの書籍