
ぼくにもわかるデザインパターン 第2章
|
|
GoFパターンカタログ
本章では,GoF本に紹介されている23パターン(表1)すべてをカタログにして紹介します。クラス図と適用例に示したJDKのソースなどを参考にして,GoFのデザインパターンの基本概念を確認していきましょう。それでは,次からさっそく始まりです。
表1 GoF 本のデザインパターンと本特集のたとえ話

Abstract Factory
ハイグレード車用部品とローグレード車用部品を作る下請け工場
たとえば
自動車メーカTOYATAは,人気車種カラーラを生産しています。カラーラには2,000ccエンジン,本皮シート使用のハイグレード仕様と,1,800ccエンジン,布シートのローグレード仕様の2車種あります。いずれの部品も,製造はすべて下請けの勝どき機械に任せており,TOYATAは勝どき機械が作った部品を組み立てて製品にし,販売しています。勝どき機械には,TOYATAから「ローグレード車種何台分の部品」のように発注がくるため,管理のしやすさを考えて部品の製造工場をグレード別に分けています。
一方TOYATAでは勝どき機械が製造した部品を1箇所に集め,同じ工場で同時に2車種の組み立てを行っています。それができるのは,それぞれの部品がグレードが異なっても取り付け方は同じになるように設計されているからです。
パターンの解説
表1と図1のように,ConcreteFactoryはAbstractFactoryインタフェースを実装しているので,ClientはどのFactoryに対しても,一様にProduct群の生成を依頼することができます。ClientからProduct群の生成の依頼を受けると,ConcreteFactoryは自分の役割のProductを生成し,Clientに返します。また,各ProductはAbstractProductインタフェースを実装していますので,ClientはどのFactoryで生成されたものか意識せずに各Productを使用することができます。つまりAbstractFactoryパターンとは,互いに関連するProductの集合をFactoryを変更することによって生成し,かつそのインタフェースを提供することによって,Clientから具体的なProductクラスを隠蔽するためのパターンです。
先の例でいえば,TOYATAは下請けに対しどの車種用の部品が欲しいのかを伝えれば下請けからその部品が届き,またTOYATAはどの車種用の部品か意識せずに一様にそれらの部品を使ってカラーラを組み立てることができるのです。
図1 ● TOYATA と下請工場(Abstract Factory)のクラス図

適用例
GoF本では例としてユーザインタフェースツールキットを挙げています。たとえばJavaはどのプラットフォームでもそのまま動くことを売りにしています。WindowsとMacではユーザインタフェースが異なっていますが,java.awt.ToolkitがAbstractFactoryとして動作することで,そのインタフェースの差異を吸収し,WriteOnce,RunAnywhereを実現しているのです。
●表2 例とGoF 本の対応(Abstract Factory)

Adapter
どこでも充電できる携帯電話用変換コネクタを作ります
たとえば
友人の家に泊まったとき,ふと気づくと携帯電話の電池がもう残りわずか。しかし明日の朝9時に,バイトの面接結果が携帯電話に掛かってくるのです。
こんな経験したことはありますか?そして,その後の行動はこんな感じではないでしょうか。
・ 友人の充電器を試してみる
期待はしてなかったものの,やはり友人の充電器のプラグとあなたの携帯電話の差込口がぜんぜん合わない。だって,友人の携帯はJ社で,あなたのはD社ですものね。
・ コンビニにダッシュ
なんと「携帯充電サービスやってます」と入り口に書いてあります。しかし,どれを試しても全然ダメ。メーカ,機種ごとに微妙に規格が異なっていて接続できません。
さて,携帯の充電器の「目的」はどのメーカ,機種でも変わらないのに,プラグの形が携帯の差込口と少しでも合わなければ充電することはできません。それなら変換コネクタを自分で作ってしまおうというのが,Adapterパターンの考え方です。
パターンの解説
Adapterパターンは,あるクラスのインタフェースをクライアントが求める他のインタフェースへ変換するときに用いられるパターンです。今回の例では,友人の充電器のプラグ(Adaptee)を変換コネクタ(Adapter)であなたの携帯電話(Client)が求めるインタフェース(あなたの携帯と接続する)に変換します。
このパターンの利点としては,クライアントが要求するインタフェースを持たない既存のクラスを利用したい場合に,クライアントと既存のクラスのどちらも修正しなくて済むというところです。クライアントや既存のクラスを修正するということは,友人の充電器のプラグをあなたの携帯へ差し込めるように,携帯電話の差込口や充電器のプラグをゴリゴリと削ることを意味します。
この変換コネクタ(Adapter)があれば,友人の充電器を使ってあなたの携帯電話を充電することができますね。
図2 ●変換コネクタ(Adapter)のクラス図

適用例
J2SEではjava.awt.event.KeyAdapterなどで使われています。
●表3 例とGoF 本の対応(Adapter)

Bridge
携帯電話の機能と実装を別々の階層で拡張する
たとえば
A君は携帯電話の開発を行うX社に入社した新入社員ですが,なぜか最近顔色がすぐれません。X社の携帯電話の開発工程が芳しくないことを知ったからです。
なんとX社では,新しい機種を開発するたびに「電話をかける」「電話を受ける」などの基本機能の設計を,1から作り直しています。さらに,それらの基本機能を利用するインタフェースは機種ごとに少しずつ異なっているため「ワンボタン機能」といった機能追加でも,個別のインタフェースに合わせて設計をやり直さざるを得ない状況です。これでは,時間とコストがかかりすぎます。この事態を回避するためには,携帯電話にとっての基本機能を共通化し,それをベースに拡張機能を追加するという考え方が有効になります。
パターンの解説
Bridgeパターンは,共通機能を実現する「実装」のクラス階層とそれを使った拡張機能を実現する「機能」のクラス階層を,明示的に分離するためのパターンです。携帯電話では,まずその共通機能をImplementorインタフェース(携帯電話の実装)として定義しておきます。そして,実現方法の違いに合わせてConcreteImplクラス(PHS型やcdmaOne型携帯電話)を実装していきます。一方,Implementorで定義した共通機能を使って最低限の基本機能を持った携帯電話を実現するのが,Abstractionクラス(携帯電話)です。なおAbstractionクラスは,その処理をImplementorのインスタンスに委譲しています。そして,Abstractionクラスを拡張して,RefinedAbstractionクラス(ワンボタン対応携帯電話)を実装していくことになります。
以上の利点をまとめると以下になります。
・ 「機能」の追加に対して,既存のすべての「実装」が対応できる
携帯電話クラスを拡張して「音声認識ダイアル機能」を追加した場合,「PHS型携帯電話」と「cdmaOne型携帯電話」は同時にその機能に対応可能です。
・ 「実装」の追加に対して,既存のすべての「機能」が対応できる
新たに「FOMA型携帯電話」を追加した場合,この携帯電話は自動的に「ジョグダイアル機能」や「ワンボタン機能」に対応することになります。
このように「実装」と「機能」のクラス階層を分けると,機能の組み合わせによるクラス数の不用意な増加を抑えることができます。
図3 ●携帯電話の機能分離(Bridge)のクラス図

適用例
J2SEでは,java.awtパッケージとjava.awt.peerパッケージの間でBridgeパターンが適用されています。ボタンクラスでは,Implementor役であるjava.awt.peer.ButtonがOSなどによる違いを吸収し,Abstraction役のjava.awt.Buttonがその機能を拡張する形でボタンの機能を実現しています。
●表4 例とGoF 本の対応(Bridge)

Builder
1つの料理手順で多国籍料理を作る
たとえば
ある多国籍料理屋では,料理長の指示のもと,韓国料理人,アメリカ料理人といった各国料理人が腕を振るっています。韓国料理人が作る肉料理は焼肉ビビンバで,アメリカ料理人が作る肉料理はステーキです。
韓国料理人やアメリカ料理人が行う料理手順は,下ごしらえ/調理/もりつけと共通しています。料理長は料理人に対して,素材となる肉を提供し,基本的な手順を指示します。指示があると各料理人が調理を始めます。
お客さんから焼肉ビビンバの注文がありました。料理長はおいしい肉を吟味し,韓国料理人に下ごしらえ/調理/もりつけの指示を出します。韓国料理人は,料理長の指示に従って焼肉ビビンバを作ります。「下ごしらえ」の指示で焼肉用のタレを作り,「調理」の指示で肉を焼き,具とご飯をいためます。「もりつけ」で,どんぶりに盛ります。こうして焼肉ビビンバのできあがりです。同じようにステーキの注文があれば,料理長は同じ指示をアメリカ料理人に出します。このように,同じ料理手順で異なる肉料理を作るといった目的を実現するためにBuilderパターンは使われます。
パターンの解説
「料理人」は,肉料理を作る料理手順のみをインタフェースとして定義しているBuilder(建築者)です。ConcreteBuilder(具体的な建築者)である「韓国料理人」や「アメリカ料理人」は「料理人」のインタフェースを継承し,肉料理の料理方法を個別に実装しています。
Directorである料理長は料理の注文を受けると,料理人(Builder)に「下ごしらえをしろ」といった指示を出します。たとえば注文された料理が焼肉ビビンバのとき,韓国料理人(ConcreteBuilder)が料理長の指示に従って焼肉ビビンバ(Product)を料理していきます。その結果,できあがった料理がお客さんのもとへ届くことになります。
Builderパターンを用いることにより,新しいConcreteBuilderクラス(たとえばメキシコ料理人)が追加されても,Directorクラスが影響を受けないで済む(「料理人」への指示ができれば料理は作れる)というメリットがあります。このように1つの作成過程で異なる製品を作りたいときに,Builderパターンは役立ちます。
図4 ●料理長と料理人(Builder)のクラス図

適用例
オブジェクトをCSV形式などのファイルに保存/復元するしくみを作る場合に,読み込んだファイルの種類に応じて対応する元のオブジェクトを復元する部分で,Builderパターンの適用が考えられます。
●表5 例とGoF 本の対応(Builder)















