どうもこんにちは。古家です。
引き続き読書会を開催しています。
題材としては「デザインパターンとともに学ぶオブジェクト指向のこころ」です。
事前準備(一応途中参加も受け付けております。外部からの参加も全然OKです)
- appear.inにて参加(参加希望の方はfuruya0102@growth-and.comまでご連絡ください)
- 音声通話の準備(マイクとヘッドホンの準備。ヘッドセットがあれば楽です)
- 課題図書の購入 (オブジェクト指向のこころ)
- 範囲の理解
大まかな流れ
- 全員が事前に範囲を読んでおく(この時、わからない点や思ったことをメモっておくと良い)
- 説明役を一人決め、その説明役が当日説明をする
- 次の日時、範囲、説明役(及び説明代理)を決める
- ブログに参加者、範囲、レビュー等の情報を掲載
今回の読書会範囲
- 第11章 Abstract Factoryパターン
読書会参加者
- 古家
- 玉尾
- 清水
今回の感想
今回は説明者は玉尾さんだったのですが仕事が立て込んでいるということで古家が代行してやりました。いよいよ半分過ぎということで慣れてきたかなと思いきや著者の言わんとしていることがなかなかに難解で(翻訳も問題ではあるが)、途中文章をなぞって読んでいくだけだったり脱線したりが多かった気がします。
第11章 Abstract Factoryパターン
このパターンはGoFの分類になぞらえると生成のパターンに分類されるものです。ファクトリと聞くとデザインパターンにおいてはよく聞くパターンではないでしょうか?
しかしAbstractFactoryの構造もやっていることも単純ではあるのですが、実際のところ有効性をはっきり理解できている人ってどれだけいるんだろうか・・・って正直なところ思います。というのも、OOPで書けるぜ!っていう人でも技術レベルはピンキリで、ちゃんと問題(プロジェクトにおける流動的要素)を分析できていたり、アダプティブなコーディングができていたりする人は少数だからです。
AbstractFactoryが行おうとしていることは生成と使用の分離にあります。
生成と使用自体の意味するところはピンとくると思います。生成はオブジェクトをインスタンス化することであり、仕様はインスタンス化したオブジェクトを使用することです。
しかし、それがなぜ必要となるのか。なければどういうことになるのかが理解できていないとAbstractFactoryを使いこなしているとは言えないと思います。
AbstractFactoryが意味するところの使用とはインターフェイス経由での使用です。
これはインターフェイス使用を強制させることで例えば以下のような問題に対処できます。
これはバージョンごとにダブルクリックの処理が違うコンポーネントの対応です(悪い例です)。
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 |
class UseComponent { /// <summary> /// ダブルクリックをする /// </summary> public void DoubleClick() { switch (component.Ver1) { case AppVersion.Ver1: component.ClickForV1(); break; case AppVersion.Ver2: component.ClickForV2(); break; case AppVersion.Ver3: component.ClickForV3(); break; } } } /// <summary> /// アプリケーションのバージョンを表す /// </summary> enum AppVersion { Ver1, Ver2, Ver3 } |
こういう例はよくあるものですが、コードに対してバージョンごとに”ダブルクリック”という挙動を埋め込んでいる例です。
コンポーネントはどのバージョンのコンポーネントかということを知っていて、それをダブルクリックした段階で判定し各々の処理に飛ばします。
クラスの中にVersionがあるのだからクラス内にSwitchを持っていくことはできるかもしれませんが、それも根本的に処理が移動しただけで根本的な解決ではありません。
これは次のたくさんの問題を持ちます。
- バージョンが増えていくことで検討する際の個所をすべて調べ上げる。
- テストコードが非常に書きにくい。
- バージョンごとにどういう処理をするのかすっきりしない。
- さらにはバージョン以外にも検討しないといけない問題は山積する可能性がある。これが山積すると組み合わせの爆発となり、コードはあらゆるところに散りばめられてしまう。
山積する問題とは可能性でいうと
- ユーザごとに挙動が変わる
- ユーザのロケール情報に応じて日付の基準日などが変わる
- 設定情報を読み取り、それに応じて挙動が変わる
これらは何時追加されるかを予想しきるのは難しいですし可能性以外の予想だにしない対応があればそれにも対応しなければならないです。
それを一つ一つ書いていくと組み合わせがとたんに爆発してしまいコードは恐ろしい状況に陥ることは言うまでもありません。
AbstractFactoryを使うとコードは以下のようになります。
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 |
class UseComponent { public void DoubleClick() { component.Click(); } } abstract class Factory { public abstract IComponent CreateComponent(); } class Ver1Factory : Factory { public override IComponent CreateComponent() { return new Ver1Component(); } } class Ver1Component : IComponent { public void Click() { //Ver1特有の処理 } } interface IComponent { void Click(); } |
switchで処理していたところはインターフェイスのClickに分けられます。使用するときは
component.Clickとするだけで、バージョンも何もかも気にする必要はなくなります。
つまり、未来でどういう実装をする必要があるかどうかもUseComponentは知る必要がありませんし、Ver1Componentが存在していなくとも、別に何もない処理に飛ばしておけばUseComponentは気にする必要がないのです。
そういうものはAbstractFactoryにまとめてしまえば、あらゆる対応をUseComponentは気にする必要はなくなります。
拡張ポイントを作ることで責務を分離でき、大部分の処理を後回しにしてクライアントコードは開発を進めることが出来るという、2度も3度もおいしいデザインパターン。それがAbstractFactoryなのです。
ただもちろん、それを覚えたからと言ってインターフェイス(拡張ポイント)だらけにコードをしてよいかというとそれも違います。必要なもののみ書く(YAGNI)。これもまた大事なことだと思いますので検討は常に必要です。
次回について
次回は7月23日になります。対象範囲は12章エキスパートはどのように設計するのか、13章CAD/CAMの問題をパターンによって解決するで説明は玉尾さんに行っていただく予定です。