酒と泪と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 継承の記述が抜けていたため、修正

ハッカソンハウス始めました!

ハッカソンハウス
2014年2月8日からエンジニア・クリエイターのためのスペース「Hackathon House」始めました!エンジニアがプロダクトづくりに集中出来る環境を作るために『Camp Fire』のご協力よろしくお願いします!

なぜ始めたのか?

僕はアメリカの500Startupsを少しだけ訪問させて頂いたことがあります。あそこはプロダクトの可能性を目一杯引き出してくれる夢のような空間でした。僕はあんな場所を日本にも作りたいとずっと想い続けてきました。この企画を一緒にやっているくりしーさんは、『サウス・バイ・サウスウエスト』を通して、「あのワクワクする空間、熱気溢れるカオスな空間を日本でも創りたい。」というビジョンで一緒にやっています!

どんな場所を作りたいのか?

僕が目指しているのは、日本を代表するようなプロダクトが芽吹く場所。そんなプロジェクトをやりたい本気な仲間と集まれる場所。そんな仲間を見つけられる「ルイーダの酒場」のような場所です。

また、プロダクト開発のために「仲間と一緒に」どこよりもリラックス・集中できる空間をコンセプトにHackathon Houseを育てていきたいです!



押さえておきたい書籍

いかがだったでしょうか?
もし説明がわかりにくかったり、間違っている場所があればぜひ一言!

Comments