読者です 読者をやめる 読者になる 読者になる

俺の報告

RoomClipを運営するエンジニアの日報(多分)です。

日報 #99 - デザインパターン:ファクトリーメソッドパターンについて

#99まできました。
もう意味がどうとか言いません。
とにかく#100まで行ききって、その後から「どうせならQiitaに投稿すればよかった」などと考えましょう。

さて、今日もGoFデザインパターンについて。
第2回は「Factory Methodパターン」です。

モノの本やらWikipediaやらを見てみればすぐ感じると思いますが、 このパターンの利便性をいきなり掴むのはそこそこ難しいです。
初見では大体の人が狐につままれたような感覚になるのではないでしょうか。
「意味は分かるが、それがどうした?」的な。
具体的な例をみても、「便利っぽいけど、そんな大げさなシステムいるのか?」と感じることだと思います。
少なくとも僕はそうでした。
ですが、しばらく時間がたってみて、改めてこのパターンをみてみると「うーんなるほどな」としみじみ思うようになりました。
これは僕の個人的な見解ですが、 「がしがしコード書くぜ!」っていうようなプログラマの視点ではなく、
もう少し抽象的なレイヤーでプログラムを設計する人の視点でこのFactory Methodパターンを見てみると、
その意味と利便性が理解しやすくなると思います。

とはいえ、最初はとにかく中身を見てみる必要があります。
なので、愚直にコードを眺めていきましょう。

このパターンのサンプルコードをは大体以下の流れで紹介されます。

まず、「Factoryクラス」なる不気味な工場と、そこで作られる予定の「Productクラス」なる得体のしれぬ商品についての抽象クラスを作り出します。
そして、その2つの抽象クラスを継承した実体クラスをそれぞれ1個ずつ作成した後、
そのFactory実体クラスにProduct実体クラスを幾つか作らせるでしょう。

newすりゃできるようなProduct実体クラスを。

そして頼んでもないのに別のProduct継承クラスを作って見せて、
「ほらご覧、実体が分からないProductでも全て同じようにFactoryクラスで作れるんだよ」などとドヤ顔で言い出します。
あのありがたいGoFデザインパターンが言っていることなので、ひとまず「なるほど」と頷くでしょうが、
穴があったら「newしちゃいかんのかーーー!」と王様の耳を告発する青年さながらの勢いで叫びたくなるのが人というものです。

とにかく、こんな感じで紹介されます。
さらに「このパターンのメリット」みたいな項目では、大体こんな感じで書かれているでしょう。
いわく『オブジェクトの生成処理と使用処理を分離できる』とか、
『アプリケーションに特化したオブジェクトの生成をサブクラスに追い出し、クラスの再利用性を高める』とか、
まぁそんなことが書かれています。
100%正しいんですが、ハイパー意味不明なわけです。

ということで、もう少し噛み砕いてFactoryパターンを眺めてみます。
Factoryパターンは前回のSingletonパターンととても似ている部分があります。
それは外部から欲しいインスタンスを適当に作ったりしない、ってところです。
その代わりに、FactoryクラスにProductクラスのインスタンスの生成を依頼するのです。
newすりゃできるものをわざわざこうするには理由があるはずです。
この理由さえわかれば、Factoryパターンの肝は掴んだようなものなのですね。

この理由はいくつか考えられます。
例えば「大体同じ機能なのに、少しの違いでnewするクラスを分岐しなきゃいけないとき」とかです。
よくされる具体例はcsvxmlの話です。
外部ファイルを読み込んで、リストを表示するようなプログラムを書くとき、
csvファイルでもxmlファイルでも同じように書きたいです。
だけど、2つのファイルの形式はあまりにも違いすぎて、読み込んで吐き出すクラスの実体は異なります。
擬似的にCSVReadクラスとXMLReadクラスというのがあったとして、
読み込み元のファイルがcsvならnew CSVReadだし、xmlならnew XMLReadしなきゃいけないのです。
それはかったるいし、保守性も下がります。
なので、Factoryに対してとにかく「ファイルを読んでその内容をリストにするようなProductを生成してくれ、ファイルはCSVな」という風に投げれば、
透過的に欲しいクラスを生成できるわけです。

こんな感じなのがFactoryの利点としてよく挙げられると思います。
まぁ納得はできます。
で、実際便利です。
ですが、本当に「newするときに細かい分岐とか条件があるから、生成処理を依頼する」ためにFactoryにするのでしょうか。
なんかそれだけの機能実装なら、抽象クラスとかコンストラクタで工夫したりとかすれば出来そうな感じがします。
わざわざFactory、Productなんていう抽象クラスの関係性を記述する必要があるのでしょうか。

そして、冒頭部分に戻ります。
プログラム全体の設計者の目線です。
プログラムの設計は時に抽象的になります。
上記の例で言えば「なんらかのファイルを読み込んで、書き出す機能を持ったクラス」程度にしかクラスの記述をしません。
する必要がないからです。
このクラスをAbstractなProductクラスだとします。
さて今、そのProductクラスを実際に使いたい別のAbstractなクラスが出現したとします。
そのクラスの中で、Productクラスをインスタンス化したいのですが、
如何せん、Productクラスは抽象クラスですので、newできないのです。
だから、newしたように見せかけるメソッドを作る必要にかられるわけです。
これがFactory Methodです。
つまり、Factory Methodパターンは、抽象的なクラス同士での擬似的なnew行為だと捉えることができるわけです。
逆に言いますと、
abstractなクラス同士の関係性は、お互いがabstractである以上、
そもそも「newできない」んです。
だから「インスタンスをクリエイトする」なんていう仰々しいメソッドが必要になるんですね。

そういう観点で改めてFactoryパターンのコードを見てみると、 「抽象Factoryクラスが抽象Productクラスを抽象的にnewしているなぁ」と見ることができると思います。
多分。きっと。
でもどうですか、そうすると分かりやすくなりませんか。
なるって言ってくれないと気持よく年の瀬を過ごせないので、なるって言って下さい。

なる!

現場からは以上でした。