ブログ「サイバー少年」

ブログ「サイバー少年」へようこそ!
小学六年生ごろからプログラミングを趣味にしている高校生のブログです。
勉強したことについての記事などを書いています。フリーソフトも制作、公開しています。
(当ブログについて詳しくは「ブログ概要紹介」を参照)

サイバー少年が作ったフリーソフトは「サイバー少年の作品展示場」へ

Factory MethodとAbstract Factoryの違いを考える

前回記事「円柱を傾けて転がすことについて考察」は、祝日、文化の日に書きました。

そろそろまた記事を書こうと思ったら、なんと今日は勤労感謝の日ではありませんか。

祝日限定更新とかにしますか…。


さて、今回はデザインパターンの話です。
この前はパターンマッチングの記事を書いて、パターンの話ばかりしています。


この前、GoFデザインパターン23種類をひととおり学んだのですが、なんということでしょう、ほぼ忘れてしまいました。

まあ23個もあったら無理はないかもしれませんが。

そして、覚えられない原因として、キッチリ覚えようとしてしまうというのがあるのかなと思いました。


記事の最後のほうでまた書きますが、デザインパターンって別にこれに準拠してガチガチに組み立てていくものじゃなくて、参考程度のものなんですよね。

「GoFの頭いい人はこんな作り方してたけどどう?」みたいな程度です。

そのため覚えるもクソもなくて、考え方のニュアンスというかエッセンスを吸収するだけぐらいの心持ちのほうが、応用も効く頭になっていいと思いました。

というわけで、最近は「結局なにをどうしたくてこうなったのか?」ということを着眼点において、またGoFデザインパターンを学び直しています。


今回は、以前からFactory MethodパターンとAbstract Factoryパターンは何が違うのかよくわからなかったのですが、学び直して分かったような気がしないでもないので、書いていきます。





とりあえず、両パターンそれぞれについて書いていく形にしたいですが、まずはFactory Methodからですね。

しかし、Factory Methodパターンは、Template Methodパターンの部分集合と捉えることが可能だと思います。

というわけで、Template Methodについて書いていきます。

どうでもいいですが、よくtempleteとスペルミスしてしまいます。
まあ、これはプレートという単語から派生しているんだと思いますが、プレートはpleteと綴るとはあまり考えられないので、そう考えたらtemplateですね。



・Template Method

Template Methodは超簡単です。
パターンとして意識して学ばなくても、自然に思いついてやっていた、ということも多いと思います。

一言でいえば、抽象クラスが具象メソッドを持って、その具象メソッドから自身の他の抽象メソッドを呼ぶパターンです。

この具象メソッドをTemplate Methodと呼びます。

どんなときにこのパターンを使いたくなるのかというと、親クラスで処理の大部分を書いておくものの、特定の一部機能の実装は他に任せたいときです。

実装を他に任せるパターンは色々ありますが、任せる先が子クラスとなるのが、Template Methodパターンです。


たとえば、“おはようございます”、“こんにちは”、“こんばんは”と立て続けに出力するクラスを作るとして、でも出力する方法は子クラスに任せたいとします。

C#風に書くと、こういう書き方(Template Method)で実現します。

abstract class Aisatsu {
  protected abstract void Write(string text);

  public void DoAisatsu() {
    this.Write("おはようございます");
    this.Write("こんにちは");
    this.Write("こんばんは");
  }
}


Aisatsuクラスを継承するクラスがWriteの実装を決めます。
ただし、挨拶の内容や順番など、処理の流れは親クラスが決めています。


以前、記事「本「オブジェクト指向でなぜつくるのか」の感想、要約」で私は、

抽象クラスなんてのがあるから、インターフェースを継承しているのか、機能を継承しているのかわからなくなる、インターフェースの継承は言語としてのインターフェースに任せればいいと思うと書きました

…がしかし、Template Methodパターンを見て思ったのですが、このパターンは抽象クラスがないと実現できませんね。盲点でした。

インターフェースには具象メソッド(DoAisatsuメソッド等)は書けないので、無理矢理やろうとしたら、委譲を使ってあれこれする変な感じになると思います。



・Factory Method

本題のFactory Methodパターンです。
しかし、まずFactory、ファクトリってなんでしょうか。

ファクトリとは、ある他のインスタンスを生成する機構のことです。

その機構をメソッドにしたのがファクトリメソッド、Factory Methodだといえるのはまあそうなのですが、
単純にreturn new 〇〇するメソッドを書いてFactory Methodパターンだとは、GoFデザインパターンではいわないようです。

Factory MethodパターンはTemplate Methodパターンの部分集合と言いましたね。

つまり、ファクトリメソッドを抽象メソッドにしたTemplate MethodパターンがFactory Methodと言っていいと思います。


どういうときに使うのかというと、親クラスが自身の処理において、道具として、他のインスタンスを作ってなんかやりたいというとき、

そしてその道具というのを抽象化したいというときに使います。

道具というのはクラスインスタンスのことですが、これを抽象化するということは、もちろんAPIを統一しなければなりませんので、そのためのインターフェースも定義します。

Productなんて呼ばれて、Factory Methodパターンの構成の中に入っています。


先ほどのAisatsuクラスでいうと、たとえば、出力の処理まで書いてしまうのはクラスが肥大化してダメだろうということで、出力してもらうためのクラスを使用するような設計にしようと考えたとします。

その出力してもらうためのクラスというのをとりあえず抽象クラスかインターフェースで定義します。
名前はWriterで、Writeメソッドを持たせるだけでいいですね。

その出力用のクラスは、Aisatsuクラスで作ることにしたのですが、Aisatsuクラスが直接作ってしまうと先ほどみたいに出力を抽象化できないわけです。

そこで、抽象メソッドのファクトリメソッドを定義して、そこに作ってもらうわけですね。

class Aisatsu {
  protected abstract Writer CreateWriter();

  public void DoAisatsu() {
    Writer writer = this.CreateWriter();
    writer.Write("おはようございます");
    writer.Write("こんにちは");
    writer.Write("こんばんは");
  }
}



こんな感じです。

わざわざWriterを子クラスに作らせなくても、普通にDoAisatsuの引数でWriterをもらうように、私ならしてしまうと思いますが、

それはAisatsuクラスの使用者がWriterを作成するということです。

それはよろしくなくて、あくまでもAisatsuクラスのサイドで完結する話にしたいという考え方が、Factory Methodパターンなんでしょう。

そこの利点とかが、私は経験少ないのでよく伝わってきませんが…。


長々と書いたものの、このパターンの大事なところは、子クラスに道具を作ってもらうということだけです。

記事冒頭でも書きましたが、このように「ようするに〇〇ってだけよね」という理解をしてしまうほうが、デザインパターンの学習においてはむしろ効果的だと思います。



・Abstract Factory

さて、Abstract Factoryです。
Factory Methodと何が違うのかというのが今回のテーマですが、平たく言ってしまえば同じです。

ただ、こちらのほうがより上位概念の話をしているということです。
カプセル化というのかわかりませんが、より部品化しています。

Template MethodからFactory Methodへの進化は、AisatsuクラスでいうWriteなどの実装を、他のクラスへと外部化することでした。

そしてFactory Methodではその実装を持っているクラスを作成するためのファクトリを抽象メソッドで実現しました。

Abstract Factoryは、そのファクトリさえも他のクラスへと外部化してしまおうというものです。


つまり、他のクラスに道具を作ってもらって、処理をその道具にやってもらうという、どんだけ人任せなんだよというパターンです。

しかし、そのおかげで一つ一つのクラスが小さくなるのが利点です。


もしFactory Methodに道具を作ってもらうパターンだと、道具が一種類ならいいかもしれませんが、
書き込み用の道具、読み込み用の道具、監視用の道具など多数の道具を使わなければならない場合はどうなるでしょうか。

それらのファクトリを自身のクラス内に持っていると、クラスがファクトリだらけになります。

そこで、ファクトリを全て外部のクラスに移して、そのクラスに頼んで作ってもらう形にすれば、すっきりします。

このクラスをファクトリクラスというようです。


ただ、これがFactory Methodとの混乱の原因で、ファクトリが単一の場合はFactory Methodパターン、ファクトリが数種類ある場合はAbstract Factoryパターンと呼ぶのかしらと私は最初、思ってしまいました。

ファクトリが数種類ある場合にAbstract Factoryを使いたくなるというだけで、両者の違いはファクトリの数ではなく、前述したとおり部品としてのレベルが違うということです。


ただ、両者の違いが部品としてのレベルの違いでしかないため、両者は同じ物なのです。

ようするにどちらも、道具を作ってもらうパターンでしかないわけです。

一応、別のパターンということになっていますが、同じとして理解してもいいと思います。


さて、使用例を挙げますが、またAisatsuクラスを使いましょう。

一方的に挨拶するだけでなく、最初に対話相手の名前を聞いて、その名前をあいさつに付加するようにします。

そのためには、Writerの他に、新たに部品としてReaderを追加します。

Readメソッドを持つような抽象クラスやインターフェースを定義します。
Readメソッドは読んだ文字列を返すようにします。

そして、Factory Methodパターンではファクトリメソッドを自身が持っていましたが、それを無くします。

ファクトリメソッドだけ定義されたクラス、つまりファクトリクラスとなる抽象クラスIOFactoryを定義します。

abstract class IOFactory {
  public abstract Writer CreateWriter();
  public abstract Reader CreateReader();
}

IOFactoryの子クラスがCreateWriterとCreateReaderを実装して、本物のWriterやReaderを返します。

Aisatsuクラスは、IOFactoryをもらってそれを使うように修正します。

class Aisatsu {
  public void DoAisatsu(IOFactory io) {
    Writer writer = io.CreateWriter();
    Reader reader = io.CreateReader();

    writer.Write("お名前を教えてください");
    string name = reader.Read();
    writer.Write(name + "さん、おはようございます");
    writer.Write(name + "さん、こんにちは");
    writer.Write(name + "さん、こんばんは");
  }
}


こんな感じになります。

これも私なら、DoAisatsuメソッドの引数としてWriterとReaderをもらうようにすると思いますが、わざわざファクトリを返します。

Factory Methodのときは、全てAisatsuクラスのサイドで完結する話になるという利点がありましたが、こちらはファクトリを作ってやらないといけないため、その利点はありません。


利点は何なのか考えてみたのですが、ファクトリを経由する仕組みにすると、たとえばコンストラクタでファクトリを渡しておくことが可能ですね。

あとは勝手にファクトリを使ってもらえばいいので、DoAisatsuを呼ぶたびにWriterとReaderを作ってやる必要はありません。

ならばコンストラクタでWriterとReaderを渡してやればいいじゃないかと思ったりしますが、
何度も同じものを繰り返し使えない道具だったり、使う直前に作らないといけない道具だという可能性があります。

そのときは、やはりファクトリを介して間接的に作ってもらう方法を取るのが良いということですかね。

Factory Methodでもそうですね。
この利点がファクトリを使うことの一番の利点かもしれません。




ところで、Abstract Factoryパターンでは、ファクトリクラスにおいて内部的にFactory Methodパターンを使用していると理解することができます。

抽象メソッドを架け橋として、親ファクトリクラスが子ファクトリクラスに部品作りを任せているからです。

ただ、Factory Methodパターンでは、子供に作ってもらった部品を自分が使っていましたが、Abstract Factoryでは部品を作るというメソッドをダイレクトに外部公開しています。

なのでTemplate Method的な使い方とは外れますが、まあでもFactory Methodですね。


しかし、実はFactory Methodパターンを使わないAbstract Factoryパターンの実現方法もあるそうです。

さっきまでの説明とぜんぜん違うじゃんという感じですが、ようするに抽象的なファクトリクラスを使うパターンがAbstract Factoryクラスなんですね。

Factory Methodパターンを使っているかどうかはファクトリクラスの内部事情なので、Abstract Factoryパターンであるかどうかの話には関係ありません。

Factory Methodはファクトリクラスの内部事情について言及するパターンであり、Abstract Factoryはもっと上位概念について語っているパターンなのです。


とはいっても、Factory Methodを使わないAbstract Factoryとはどうやって実現するのでしょうか。

普通にAbstract Factoryをやろうと思ったら、どうやってもファクトリクラス内でFactory Methodパターンを使ってしまうのが自然です。

しかし、Wikipediaを見ると、GoFの本に書いていたそうなのですが、Prototypeパターンを使ったAbstract Factoryパターンの実現方法というのがあるようです。

Abstract Factory パターン - Wikipedia
https://ja.wikipedia.org/wiki/Abstract_Factory_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3

これは驚きです。

詳しく書いていないので想像ですが、ファクトリクラスは継承関係を持たず、ファクトリクラスは部品役(複数種類可)のクローンを作る役目を担っているのかなと思いますね。


Prototypeパターンについて簡単にいうと、

・あるクラスに予め部品役のインスタンスを登録しておいて、そのクラスに頼めば部品役のクラスのコピー(クローン)を作ってくれる

・部品役のクラスは、自身のクローンを作成できるメソッドを持つことを保証するインターフェースを持つ

というパターンです。

本当はPrototypeパターンで言及されているのは後者の条件だけっぽいのですが、前者とセットで使うことも多いと思われます。

そしてPrototypeを用いたFactory Methodの実現方法では、もちろん前者の条件も含んでいるのでしょう。

その登録しておくクラスが、ファクトリクラスと同一だということです。
ファクトリメソッドに相当するのは、部品のクローンを作って返すメソッドです。


このクラスのインスタンスを作ったら、初期化作業として部品(のセット)を登録します。

その登録する部品を調整することによって、ファクトリクラスとしても、何を作るのかが異なってきますからね。

抽象クラスではないですが、抽象的なファクトリといえそうですね。

ただ、部品を登録する初期化作業を、使用者側のほう、AisatsuクラスでいうとAisatsuクラスを呼ぶサイドが行わないといけなくなるので、そこが難点ですね。

もちろん、応用すれば解消できる問題でしょうけども、応用しないと解消できないという…話が無駄に広がってしまう難点がありますね。


それを考えれば普通にFactory Methodでやったほうがいいですね。

Prototypeパターンでやるほうの利点があるとしたら、それはPrototypeパターン自体の利点に直結しているのではないでしょうか。




そういうわけで、デザインパターンというのは、決まった像があるわけではなく、「さっきと話が違う!」なんてことがよくあるんですね。

理系脳には非常にイラッと来ますが、デザインパターンというのはそういうものなのです。

もともと、参考にしてください程度で作ったのに、もてはやされて学問化されてしまって、キッチリ理解しようと試みる私みたいな人間が出てきてしまったという構図だと思いますが、

私はもう、それに騙されずに、たかがパターン、されど英知が詰まった魅力的なパターンとして、デザインパターンの再学習を進めていきたいと思います。

tag: プログラミング オブジェクト指向 クラス オブジェクト デザインパターン GoF 抽象 設計 ファクトリ

コメント

Factory系の両デザインパターンの差を、端的明瞭に纏めると....

【両パターンのインターフェースの共通点】
抽象メソッドであるファクトリメソッドの書式宣言を、1つ以上持つ事。

【両パターンのインターフェースの違い】
AbstractFactoryの仕様は、上述の共通点のみ。
∴基本クラスはインターフェース。

がFactoryMethodの仕様は、AbstractFactoryのそれに加え、ファクトリメソッドを使って作られたIProductを使い、何らかの処理を行う実装メソッドを持つ。
∴基本クラスは抽象クラス。

という事。

  • 2016/10/23(日) 18:07:44 |
  • URL |
  • ぶた #-
  • [ 編集 ]

Re: Factory系の両デザインパターンの差を、端的明瞭に纏めると....

なるほど、わかりやすいスタイルでの説明ありがとうございます。
たしかに、IProductを使う処理がファクトリクラスの中に入ってるか、別であるかと考えることもできますね。

コメントの投稿

トラックバック

トラックバック URL
http://cyberboy6.blog.fc2.com/tb.php/432-a31a7b84
この記事にトラックバックする(FC2ブログユーザー)

当ブログをご利用(閲覧等)になる場合は必ず「当ブログの利用規定」をお守りください。