技術トピックス

ぼくにもわかるデザインパターン 第2章 GoFパターン大カタログ ~パターンがみるみる頭にしみこむ~

UML PRESS Vol.1 2001年12月号に特集「身近な例でGoF総ざらい & サーバサイドJava ぼくにもわかるデザインパターン」

児高 慎治郎・中島 麻衣・坂口 直大
2001年12月01日
※内容は公開当時のものです

GoFパターンカタログ

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

表1 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)のクラス図

図1 TOYATA と下請工場(Abstract Factory)のクラス図

適用例

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

表2 例とGoF 本の対応(Abstract Factory)

表2 例とGoF 本の対応(Abstract Factory)

Adapter:どこでも充電できる携帯電話用変換コネクタを作ります

たとえば

友人の家に泊まったとき,ふと気づくと携帯電話の電池がもう残りわずか。しかし明日の朝9時に,バイトの面接結果が携帯電話に掛かってくるのです。こんな経験したことはありますか?そして,その後の行動はこんな感じではないでしょうか。

友人の充電器を試してみる
期待はしてなかったものの,やはり友人の充電器のプラグとあなたの携帯電話の差込口がぜんぜん合わない。だって,友人の携帯はJ社で,あなたのはD社ですものね。
コンビニにダッシュ
なんと「携帯充電サービスやってます」と入り口に書いてあります。しかし,どれを試しても全然ダメ。メーカ,機種ごとに微妙に規格が異なっていて接続できません。

さて,携帯の充電器の「目的」はどのメーカ,機種でも変わらないのに,プラグの形が携帯の差込口と少しでも合わなければ充電することはできません。それなら変換コネクタを自分で作ってしまおうというのが,Adapterパターンの考え方です。

パターンの解説

Adapterパターンは,あるクラスのインタフェースをクライアントが求める他のインタフェースへ変換するときに用いられるパターンです。今回の例では,友人の充電器のプラグ(Adaptee)を変換コネクタ(Adapter)であなたの携帯電話(Client)が求めるインタフェース(あなたの携帯と接続する)に変換します。

このパターンの利点としては,クライアントが要求するインタフェースを持たない既存のクラスを利用したい場合に,クライアントと既存のクラスのどちらも修正しなくて済むというところです。クライアントや既存のクラスを修正するということは,友人の充電器のプラグをあなたの携帯へ差し込めるように,携帯電話の差込口や充電器のプラグをゴリゴリと削ることを意味します。

この変換コネクタ(Adapter)があれば,友人の充電器を使ってあなたの携帯電話を充電することができますね。

図2 変換コネクタ(Adapter)のクラス図

図2 変換コネクタ(Adapter)のクラス図

適用例

J2SEではjava.awt.event.KeyAdapterなどで使われています。

表3 例とGoF 本の対応(Adapter)

表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)のクラス図

図3 携帯電話の機能分離(Bridge)のクラス図

適用例

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

表4 例とGoF 本の対応(Bridge)

表4 例とGoF 本の対応(Bridge)

Builder:1つの料理手順で多国籍料理を作る

たとえば

ある多国籍料理屋では,料理長の指示のもと,韓国料理人,アメリカ料理人といった各国料理人が腕を振るっています。韓国料理人が作る肉料理は焼肉ビビンバで,アメリカ料理人が作る肉料理はステーキです。

韓国料理人やアメリカ料理人が行う料理手順は,下ごしらえ/調理/もりつけと共通しています。料理長は料理人に対して,素材となる肉を提供し,基本的な手順を指示します。指示があると各料理人が調理を始めます。

お客さんから焼肉ビビンバの注文がありました。料理長はおいしい肉を吟味し,韓国料理人に下ごしらえ/調理/もりつけの指示を出します。韓国料理人は,料理長の指示に従って焼肉ビビンバを作ります。「下ごしらえ」の指示で焼肉用のタレを作り,「調理」の指示で肉を焼き,具とご飯をいためます。「もりつけ」で,どんぶりに盛ります。こうして焼肉ビビンバのできあがりです。同じようにステーキの注文があれば,料理長は同じ指示をアメリカ料理人に出します。このように,同じ料理手順で異なる肉料理を作るといった目的を実現するためにBuilderパターンは使われます。

パターンの解説

「料理人」は,肉料理を作る料理手順のみをインタフェースとして定義しているBuilder(建築者)です。ConcreteBuilder(具体的な建築者)である「韓国料理人」や「アメリカ料理人」は「料理人」のインタフェースを継承し,肉料理の料理方法を個別に実装しています。

Directorである料理長は料理の注文を受けると,料理人(Builder)に「下ごしらえをしろ」といった指示を出します。たとえば注文された料理が焼肉ビビンバのとき,韓国料理人(ConcreteBuilder)が料理長の指示に従って焼肉ビビンバ(Product)を料理していきます。その結果,できあがった料理がお客さんのもとへ届くことになります。

Builderパターンを用いることにより,新しいConcreteBuilderクラス(たとえばメキシコ料理人)が追加されても,Directorクラスが影響を受けないで済む(「料理人」への指示ができれば料理は作れる)というメリットがあります。このように1つの作成過程で異なる製品を作りたいときに,Builderパターンは役立ちます。

図4 料理長と料理人(Builder)のクラス図

図4 料理長と料理人(Builder)のクラス図

適用例

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

表5 例とGoF 本の対応(Builder)

表5 例とGoF 本の対応(Builder)

Chain of Responsibility:業務仕様の質問に応じる人をチェーン状につなぐ

たとえば

新人SEのAさんは金融系システムの開発に携わっていますが,まだまだわからない業務仕様が多いのです。そこでAさんは不明な業務仕様があると,プロジェクトリーダのBさんに質問します。

Bさんは業務仕様について知っていれば回答しますが,わからないものや,まだ決まっていない仕様の場合は発注元であるY銀行のCさんに問い合わせます。Cさんは問い合わせのあった業務仕様について回答してくれます。しかしCさんでは判断がつかない場合もあり,質問の種類に応じて営業担当のDさんや融資支援担当のEさんに業務仕様を問い合わせます。

このように,要求を処理する人が複数いて,誰が処理するのか決まっていない場合にChainofResponsibilityは有効です。

プロジェクトリーダのBさんを通さないで,お客さんであるCさんに直接聞いたほうが早いかもしれません。しかし毎回Bさんに質問していれば,Aさんは誰が回答をくれるかを知らなくても済みます。担当がCさんからYさんに変更になったとしても,プロジェクトリーダのBさんだけがそのことを知っていれば済むのです。

パターンの解説

新人SEであるAさんの質問を受けつけるBさんやCさんを,総称してHandlerと呼びます。Handlerでは,Aさんの質問を受けるというインタフェースを定義しています。Handlerの役割は「要求を処理するためのインタフェースを定義する」ことなのです。

プロジェクトリーダのBさんやY銀行のCさんといった,Aさんの質問を受ける各個人はConcreteHandlerです。この人たちがAさんの質問を実際に受けつけます。

また,質問を受けつける各個人は皆,次に質問を受けつけてくれる人のことを知っています。もしもBさんがAさんの質問に答えられなければCさんへ,Cさんが答えられなければDさんへと質問に答えてくれる人をたらいまわしにしていくのです。

図5 新人SE と業務質問(Chain of Responsibility)のクラス図

図5

適用例

J2EEパターンのInterceptingFilterはChainofResponsibilityを適用しています(注1)。また,サーブレットエンジンのTomcatでは,要求の送信オブジェクト(Tomcatコア,サーブレット)と実際の受信オブジェクト(要求されたオペレーションを実行するモジュールの1つ)の結合を避けるために,インタセプタと呼ばれるメカニズムを採用しています。これにChainofResponsibilityが適用されています。

表6 例とGoF本の対応(Chain of Responsibility)

表5

Command:さまざまな命令をマス○さんに実行させることができます

たとえば

マス○さんの趣味は昼寝。3度の飯より昼寝好きです。よく晴れた休日の午後など,縁側で愛猫のドラと仲良くゴロゴロしています。そうなるともうだめです。テコでも動きません。サザ○さんはそんなマス○さんが不満です。こっちは食事の準備/掃除/洗濯/寝たきりになった波○じいさんの世話までしないといけません。少しくらい手伝ってもらわないとやっていけません。それなのに,マス○さんは命令されるけど聞こえなかった,証拠がない,覚えられないと言い逃れをし,いつも縁側でゴロゴロ。

とうとう頭に来たサザ○さんは考えました。お願いしたいことを紙に書いておき,やってほしいときに縁側のコルクボードに貼るのです。そうしておけば証拠も残るし,その紙も何度も使いまわすことができます。ある休日の朝,サザ○さんは「風呂掃除」命令をコルクボードに張りました。はたして,マス○さんはサザ○さんの思惑通りやってくれるでしょうか。

パターンの解説

Client(サザ○さん)はReceiver(マス○さん)への命令(ConcreteCommand)を作成します。Clientはその命令を実行したいときにInvoker(コルクボード)に命令を渡すと,InvokerはそのConcreteCommandを実行します。この命令を取り消し可能にする場合は,Invokerは実行前の状態をセーブしておきます。また,何の命令を行ったかもここで管理します(注2)。

ConcreteCommandはReceiver(マス○さん)に対して行動の呼び出しを行い,Receiverはその呼び出しどおりに行動します。このように命令をする人(Client)と命令を実行する人(Receiver)がCommandによって分離され,それによってCommandの再利用と履歴管理を可能にしています。これがCommandパターンです。

応用として「夕飯を作る」命令の後に必ず「洗い物をする」命令を行わせる場合,CommandパターンにCompositeパターンを組み合わせるということもできます。たとえばExcelのマクロはこのやり方で実装できそうです。

図6 ます○さんとさざ○さん(Command)のクラス

図6 ます○さんとさざ○さん(Command)のクラス

適用例

Commandパターンの実装例については,第3章の事例を参照ください。

表7 例とGoF 本の対応(Command)

表7 例とGoF 本の対応(Command)

Composite:営業部の階層構造を簡潔に表現します

たとえば

某メーカの営業部には,管理職と営業職の区別がありません。営業部に所属している社員は部長であろうが課長であろうが,平社員であろうが外回りの営業を行います。また社員の中にも,正社員と契約社員がいます。正社員と契約社員の違いは,正社員には部下がつきますが,契約社員には部下がつかないことです。また社員の評価は,すべて売上成績に基づいて行われます。その営業成績は,部下の営業成績が合算された形で計算されます。これには正社員も契約社員も,区別はありません。ある日人事部からAさんに問い合わせがありました。「君のところの営業成績は?」

Aさんには直属の部下が3人います(図7-1)。2人は契約社員で,1人は正社員です。正社員にも部下がおり,部下の合計は全員で5人になります。全員の成績を計算するのは面倒そうですが,Aさんは直接営業成績を尋ねるのは直属の部下3人で済みます。なぜなら彼らの部下の成績は,彼らが集計してくれるからです。Aさんにとっては部下が正社員か契約社員なのかということは関係ありません。

図7-1 営業成績の計算

図7-1 営業成績の計算

図7-2 営業部階層構造(Composite)のクラス図

図7-2 営業部階層構造(Composite)のクラス図

パターンの解説

「営業部社員」Componentは,Compositeである正社員とLeafである契約社員の共通の性質をまとめたものです。

CompositeもLeafもComponentを継承しますので,営業成績を計算するメソッドと,部下を追加するメソッドを実装しなければなりません。これにより,人事部はその人に聞くだけで,その人の部下も含めた営業成績を得ることができます。ただ,Leafは部下に対して追加できないので,部下を追加するメソッドは空にするか,追加しようとしたときに例外をスローするなどの必要があるでしょう。

このようにCompositeパターンは,ClientがLeafなのかCompositeなのかを意識しなくても統一した操作を可能にし,それがCompositeの場合は再帰的に操作を繰り返してくれます。

適用例

Windowsなどのファイルシステムで使われています。Leafがファイル,Compositeがフォルダです。あるフォルダを別の場所に移動すると配下のファイル/フォルダとも同じように移動します。これはまさにCompositeパターンと言えるでしょう。

J2SEではjava.awt.ComponentとContainerやButtonなどの関係がCompositeパターンになります。

表8 例とGoF 本の対応(Composite)

表8 例とGoF 本の対応(Composite)

Decorator:エフェクタを使えば,チープなギター音がホットなサウンドに早変わり

たとえば

ミュージシャン志望のハンス君は,近くの商店街でエレキギターを衝動買いしました。部屋に戻っておもむろに「ドレミファソラシド」の練習をはじめましたが,なんだかピンときません。

「俺が求めていたサウンドはこれじゃない!」思わずギターを叩き壊しそうになるハンス君。しかし「エフェクタ」という機械をギターとアンプの間につなげばいろいろな音色が得られることを知りました。さらに,いくつも重ねて通すことでいろいろな複合効果が生まれるということも覚えました

「これなら俺だけの音がつくれるぜ!」ハンス君はさっそく「エフェクタ」なるものを購入しに商店街の楽器屋へと走り出しました。ハンス君のチョイスしたエフェクタはリバーブ(空間的な残響効果),コーラス(音を重複させる),ディストーション(ヘビメタ風),コンプレッサ(一定音量を保つ)など4つです。

気がはやいハンス君は,とりあえず買ってきたすべてのエフェクタを重ねて通して,フルボリュームでギターをかき鳴らしました。

「ギョイィィィィィンーーーー」

パターンの解説

Decoratorパターンは,継承を用いずに委譲を使って機能拡張をします。その拡張する様子が,さも機能を装飾していくように見えるのでDecorator(装飾者)と呼ばれます。

委譲を使った機能拡張のメリットは,装飾する芯となるComponentを継承したConcreteComponentにDecoratorを継承したConcreteDecoratorを付けたりはずしたりして,実行時に簡単に機能の追加や削除を行えることです。また,重ねて使うこともできます。

今回の例では,最初は痩せた物足りない音を返すだけのギタークラスの「響く」をConcreteDecoratorである各エフェクタを通すことにより,いろいろな音響効果をもった音を返す「響く」に拡張することができましたね。

図8 エフェクターとギター(Decorator)のクラス図

図8 エフェクターとギター(Decorator)のクラス図

適用例

J2SEでは,java.io.OutputStreamがDecoratorパターンを適用しています。

表9 例とGoF 本の対応(Decorator)

表9 例とGoF 本の対応(Decorator)

Facade:「おまかせ洗いボタン」一発だとらくちんです

たとえば

最近の洗濯機はすっかり便利になりました。単に全自動であるに留まらず,衣料の素材別に洗い方を変えたりでき上がり時間を指定したりと,数多くの便利な洗濯機が作り出されています。しかしいくら便利になっているとはいえ,洗濯機の持つ代表的な機能は,「洗う」「すすぐ」「脱水する」の3つです。

ここで「洗う」機能を考えてみると,「30分かけて洗う」「ひどい汚れは強めに洗う」「化繊類は丁寧に洗う」など,さまざまな洗い方をあなたは選ぶことができます。同様にすすぎや脱水についても,時間や処理方法を事細かに指定することができます。

ところで,あなたは普段洗濯をするときにいちいち洗い方や仕上げの内容を指定したりするでしょうか?そう,たいていの全自動洗濯機なら付いている「おまかせ洗いボタン」を押して,あとはほったらかしですよね。でも大切なセーターを洗うときなどは,あなた自身が「洗濯」「すすぎ」の指示を細かく指定して使うこともできます。

このように,状況に応じて,自分で洗濯機の細かい機能を指定したり,ボタン1つでらくちんに済ませたりと使い分けることができるのです。デザインパターンでも,Facadeと呼ばれるパターンを使うことにより,簡単な操作であなたに代わって洗濯をしてくれる,この便利な「おまかせ洗いボタン」を実現することできます。

パターンの解説

Facadeとは,フランス語で「正面窓口」という意味です。Facadeパターンは,Client(依頼人)に対して単純化された高レベルのインタフェースを提供します。当然あなた(Client)自身は「洗濯」クラスや「すすぎ」クラスといった低レベルのインタフェースを使用することもできますが,時間がなくて適当に洗濯したいだけのClientにとっては,これらは多少荷の重いインタフェースだといえます。

そこで,あなたに代わって「洗濯」クラスや「すすぎ」クラスのメソッドを呼び出して適当に洗濯を行う「おまかせ洗いボタン」クラス(Facade)を用意するのです。もちろん便利な反面細かい操作はできなくなりますが,Clientの要求のレベルによってはよりシンプルで扱いやすいインタフェースとなります。

このようにFacadeパターンを用意すれば,Clientは巨大で複雑なクラス群を利用する際に,低レベルなインタフェースを意識せずに,必要な機能だけを備えた高レベルなインタフェースを使用することができるのです。

図9 洗濯機の機能(Facade)のクラス図

図9 洗濯機の機能(Facade)のクラス図

適用例

J2SEでは,java.net.URLクラスでFacadeパターンが適用されています。

表10 例とGoF 本の対応(Facade)

表10 例とGoF 本の対応(Facade)

Factory Method:実際の衣料品の製造は各々の工場で行います

たとえば

(株)UNIQUEは国内有数の衣料品メーカです。主力商品にはフリース/チノパン/ジャケットがありますが,生産性を重視するため,A工場ではフリース,B工場はチノパン,C工場はジャケットというように各工場で決められた商品だけを製造しています。

また工場から店舗への配送もコスト削減のため,一括して白猫運輸に委託しています。工場は白猫運輸に衣料品を渡し,白猫運輸はその衣料品がジャケットであるかフリースであるかは意識せず,決められた店舗へ配送します。

工場は,営業部からの依頼で衣料品を製造します。製造した衣料品はいったん工場で在庫として保持しておき,営業部からの依頼で,各店舗に配送するため白猫運輸に渡します。

パターンの解説

具体的に何を製造するかは各工場によってばらばらのため,製造する役割はサブクラスであるConcreteCreatorで定義しなければなりません。ところがサブクラスで製造された衣料品をスーパークラスが知らなければ,配送を白猫運輸に依頼することができません。まさにこんなとき,FactoryMethodパターンが有効です。

ClientがCreatorに対し「衣料品を製造する」メソッドを呼ぶと,CreatorはサブクラスであるConcreteCreatorの「製造する」メソッドを呼びます。ConcreteCreatorはConcreteProductを生成し,Productとして返します。Creatorでは受け取ったProductを保持しておき,Clientからの要求でそのProductを操作します。

すなわちFactoryMethodパターンとは,スーパークラスでそのインスタンスを扱いたい(工場が衣料品の配送を白猫運輸へ依頼したい)が,スーパークラスではそのインスタンスを生成することができない(何を製造するかはサブクラスである各工場によって異なる)とき,スーパークラスではインスタンスを生成するためのインタフェースだけを規定し,具体的なインスタンスの生成は各サブクラスで行うというパターンです。

ちなみに,FactoryMethodパターンはTemplateMethodパターンの応用事例です。工場クラスの「衣料品を製造する」メソッドが呼ばれたとき,具体的な処理はサブクラスの「製造する」で実装します。この例では,FactoryMethodパターンはインスタンスの生成をTemplateMethodパターンで実装しているとも言えるでしょう。

図10 衣料品工場(Factory Method)のクラス図

図10 衣料品工場(Factory Method)のクラス図

適用例

java.net.URLConnectionのgetContentメソッドはFactoryMethodパターンを使用しています。

表11 例とGoF 本の対応(Factory Method)

表11 例とGoF 本の対応(Factory Method)

Flyweight:体操服を共有して出費を節約します

たとえば

12人兄弟のお母さんは,それぞれの子供たちが学校に入学するたびに必要なものを買い揃えてあげなければなりません。文具,本,かばん,体操服…,必要なものはたくさんあるのに12人分なんて。考えただけでも大変な出費です。そこでお母さんは考えました。「中学校の体操服を兄弟たちで使いまわすようにして,かかるコストを抑えよう!」

お母さんは長男の入学時に中学校の体操服を1つだけ買い,あとは兄弟たちで使いまわすようにしました。このとき,12人は同じ中学校に通うので体操服のデザイン,中学校の校章マークはみんな同じです。ただ,名前とクラスがかかれている名札だけはそれぞれ12人で違うので,体操服を使うときに付け替えるようにしました。そうすれば,うまく兄弟たちで使いまわすことができます。

…というように,毎年毎年12人の兄弟たちに体操服を買うのではなく兄弟で体操服を共有することにより,出費を抑えることができます。

パターンの解説

Flyweightパターンは,類似のインスタンスを多数利用するときに「インスタンスを共有して使いまわす」パターンです。 このFlyweightを実装したものをConcreteFlyweightといいます。たとえ話でいうと,Flyweightが体操服で,ConcreteFlyweightが実際に使いまわす中学校の体操服です。また,Flyweightの管理者をFlyweightFactoryといいます。FlyweightFactoryは,Clientから「該当するFlyweight」の要求がきたときに,そのFlyweightインスタンスを返す役目をします。たとえ話ではお母さんがFlyweightFactory,12人の兄弟たちがClientにあたります。兄弟たちがお母さんに中学校の体操服を要求すると,お母さんから共有する体操服をもらえるというわけです。

ここで,共有するインスタンスはまったく同じものでなくてもOKです。共有できる情報は共有インスタンスに持たせ,共有できない情報はインスタンスを利用する際にそのインスタンスに渡すことにより,インスタンスの再利用を効率よく行うことができます。

体操服については,みんなで共有する情報として,体操服のデザインや中学校の校章マークなどがあります。

そして,共有せず体操服を利用する際に与える情報としては,名札(名前,クラス)があるでしょう。 このように共有できる情報と共有できない情報を切り出すことにより,インスタンス(中学校の体操服)を上手に共有し,効率よく利用することができます。これによりメモリ(出費)を節約することができるというわけです。

図11 兄弟と体操服(Flyweight)のクラス図

図11 兄弟と体操服(Flyweight)のクラス図

適用例

J2SEでは,java.lang.StringでFlyweightパターンが利用されています。JVM内で,同じ定数を表すStringインスタンスを使いまわすようにしています。

表12 例とGoF 本の対応(Flyweight)

表12 例とGoF 本の対応(Flyweight)

Interpreter:英文法を定義し,英文を解釈します

たとえば

「文法」と聞いて誰もが真っ先に思い出すのは,やはり英文法ではないでしょうか。「This is a book.」という文は,基本文型の第2文型(SVC)であると中学校で習ったはずです。「this」がS,「is」がV,「abook」がCです。またさらに,「a book」は「a」という冠詞と「book」という名詞に分かれます。「this」は代名詞,「is」は自動詞に分類されます。

では,もしこのような文法の解釈を行うプログラムを作るとしたらどうしますか?「正規言語」「有限オートマトン」「LR法」といったコンパイラの原理を勉強しますか?

実はここでInterpreterパターンを利用すると便利です。確かに英文法のような複雑な文法規則を表現するには多少荷が重いのですが,ちょっとした文法規則をちょっとした時間で作る場合には,非常に有効です。

パターンの解説

Interpreterパターンは,ある文法規則を定義し,与えられた言語を解釈するために利用されるパターンです。文法規則には大きく分けて2つの表現があります。

1つはTerminalExpression(終端となる表現)と呼ばれ,「代名詞::=this|that|…」といった他の規則を適用する表現です。

2つ目はNonterminalExpression(非終端となる表現)で,「英文::=SV|SVC|…」のようにさらに別の規則を内包します。

NonterminalExpressionはスーパークラスであるAbstractExpressionを集約しており,TerminalExpressionもしくはNonterminalExpressionのインスタンスを子として持ちます。つまり,NonterminalExpressionは再帰的に(文法規則が許すかぎり)いくつでも子孫となるExpressionの具象クラスを持つことができます。逆にTerminalExpressionはAbstractExpressionを集約しないので,それ以上は子を持つことができません。

このように,Interpreterパターンでは言語を解釈するロジック(文法規則)を個別のExpression具象クラスに局在化できます。これにより個々のクラスの責任分担が明確になり,少ない労力でロジックの実装を行うことができるようになります。

図12 英文法(Interpreter)のクラス図

図12 英文法(Interpreter)のクラス図

適用例

J2SEでは,java.text.FormatのサブクラスでInterpreterパターンが適用されています。AbstractExpressionの役割を担うFormatのサブクラスとしては,数値を解析するNumberFormatや日付や時刻を解析するDateFormatなどがあります。

表12 例とGoF 本の対応(Interpreter)

表12 例とGoF 本の対応(Interpreter)

Iterator:5年1組の生徒を名簿から順に取り出します

たとえば

〇〇小学校の5年1組には現在40人の生徒がいます。生徒は生年月日の順番に出席番号が付けられていて,出席番号順にクラスの名簿が作られています。先生が生徒の出席を取るときなどは,この名簿を1番から40番まで読み上げ,生徒全員の出欠状況を確認します。

しかしある日いつものように先生が40人の生徒の出席を取り終わり教室を出ようとしたら,ある生徒が「先生!私,名前呼ばれていません!」先生は1人の生徒の名前を飛ばしてしまったようです…。

このようなミスをなくすために,先生は「自動出席読取機」を買いました。その機械に生徒の情報として5年1組クラス名簿を入れると,その名簿にある生徒の名前を順に表示してくれるという機械です。機械には「次」ボタンがついており,ボタンを押すたびに1番から順に生徒の名前が表示されていきます。そして先生は,表示された名前を読み上げます。

「自動出席読取機」があると,生徒の名前が全員分順番に表示されることが保証されているので,もう生徒の名前を呼び逃がすということも起きません。

パターンの解説

Iteratorパターンとは,集合の内部構造を利用者に見せずに,その集合に順番にアクセスする(走査する)方法を提供するパターンです。

実際に走査する方法を提供するものをConcreteIteratorといい,現在集合のどの部分を走査しているかという情報を保持しています。たとえ話の中では「自動出席読取機」がこのConcreteIteratorにあたります。またこの例には出てきませんでしたが,集合の要素を走査するためのインタフェースを定義したものが,Iterator(読取機)といいます。走査する集合をConcreteAggregateといい,自分自身の集合の走査方法を提供するConcreteIteratorを生成して返します。たとえ話では,5年1組(名簿)がこのConcreteAggregateにあたります。ConcreteIteratorである「自動出席読取機」を生成して返すのですが,ここでは「先生に買ってもらう」というのがその方法です。そしてConcreteAggregateのインタフェースは,Aggregate(集合)と呼ばれます。

このパターンを使用すると,先生は生徒がどの順番で並んでるかの構造を意識せずに,「自動出席読取機」から簡単な操作で生徒を順番に取り出すことができます。また,あいうえお順や席順というような異なる走査方法で順番に取り出したいという場合も,専用の読取機さえあれば,先生はその違いを意識せずに簡単な方法で取り出すことができるというわけです。

図13 生徒名簿取得(Iterator)のクラス図

図13 生徒名簿取得(Iterator)のクラス図

適用例

J2SEではjava.util.Iteratorが用意されています。CollectionやListなどの集合からIteratorを生成することにより,利用者は集合を走査することができます。

表14 例とGoF 本の対応(Iterator)

表14 例とGoF 本の対応(Iterator)

Mediator:孔明がすべての部隊の面倒を見ます

たとえば

蜀の名軍師,諸葛亮孔明は,他国との戦いである計略を考えました。その計略は以下のとおりです。

部隊を3つに分け,まず1つめの部隊が敵の本体と遭遇したら戦いを挑み,ある程度戦ったら負けたふりをして後退します。敵は,ここぞとばかりに追撃してくるでしょう。1つめの部隊はただひたすら後退し,敵部隊をあるポイントまでおびきよせます。敵がそのポイントまで来たら,2つめの部隊は崖の上から大石や大木を敵部隊めがけて落とします。敵はあわてて退却するでしょう。それを見計らって,後退していた1つめの部隊は引き返して再び攻撃をしかけます。そして,3つめの部隊が敵の退路から敵部隊に攻撃をかけ,挟み撃ちにします。この計略の成功を握るカギは,各部隊がいかに他部隊の動きを把握しているかです。もし,各部隊が他部隊の動きを把握していなかったとしたら,大石や大木を味方の部隊に落としてしまうことにもなりかねませんし,落石をしかける前に1つめの部隊が後退をやめて攻撃を開始してしまうかもしれません。

孔明はそういったミスを防ぐために,各部隊はすべての状況の変化を孔明に報告させることにしました。孔明は各々の状況を逐次判断して各部隊に命令を出すのです。そうすることにより,各部隊は他部隊が存在していることすら知らなくても確実に任務を遂行することが可能になります。なぜなら,部隊はすべての状況を把握している孔明の命令だけを聞けばよいからです。

パターンの解説

Mediatorパターンは,オブジェクト同士がお互いを明示的に参照し合うことがないようにして,結合度を低めることを促進します。これにより,オブジェクトの相互作用を独立に変えることができるようになります。オブジェクト指向設計では,オブジェクト間に振る舞いを分配することを積極的に検討します。しかし,ときにはそれが災いして,オブジェクト間に多くの関連を生み出すことになり,各オブジェクトが他の大部分のオブジェクトを知らなければならなくなることもあります。

この例では,Colleagueを継承したConcreteColleagueである各部隊は,Mediataorを継承したConcreteMediator(孔明)だけを知っていればいいので他部隊のことを認識する必要がなく,たとえ異なる任務を遂行する部隊が追加されたとしても,彼らは頭を悩ませなくともいいのです。

図14 孔明と各部隊(Mediator)のクラス図

図14 孔明と各部隊(Mediator)のクラス図

適用例

J2SEではjavax.swing.FocusManagerなどで使われています。

表15 例とGoF 本の対応(Mediator)

表15 例とGoF 本の対応(Mediator)

Memento:メモリカードに状態を保存しておけば,途中からゲームができます

たとえば

テレビゲームが大好きな小学生マイケル君は,クリスマスに買ってもらった最新ゲーム機「カセットビジョン」で今日も楽しくゲームで遊んでいます。でも,そんなマイケル君には悩みがありました。ゲームを何時間もやっているとお母さんに「ゲームばかりしていないで勉強しなさい!」と口うるさく怒られるし,なにより,マイケル君が心から尊敬しているゲーム名人の桜田名人が「ゲームは1日1時間,僕らの仕事はもちろん勉強,成績上がればゲームも楽しい,外で遊ぼう元気よく,僕らは未来の社会人」といつもテレビで言っているからです。

でも,きまって1番いいところで1時間経ってしまい,泣く泣く電源を切るはめになってしまいます。1度電源を切ると,再開するときはまた1番最初からになってしまいます。

マイケル君は,ゲームを前回電源を切ったところからまた始められたらいいなあと思いました。そうすれば,時間を気にすることなく,ゲームをすることができます。しかしそこは,さすがは最新機種カセットビジョン。外付けメモリカードにゲームの途中の状態を保存することができ,メモリカードに保存さえしておけば,電源を切ってしまっても,次は前回保存した状態からゲームを再開できるのです…でも別売り。貯金していたおこずかいでメモリカードを買ったマイケル君。これで,お母さんにゲームのやりすぎで叱られることもないし,桜田名人との約束も守ることができますね。

パターンの解説

Mementoパターンは,インスタンスの内部状態を外部に保持しておき,インスタンスをいつでもこの状態に戻すことができるようにします。Mementoには,「思い出させるもの」「記念品」「形見」「忘れ形見」といった直訳があり,Originatorの内部状態を保持する役割を持っています。(マイケル君)なのですが,基本的にはCareTakerはMementoの中身を知ることはできません。Mementoの中身を知り自由にアクセスできるのは,Originator(カセットビジョン)だけです。

この例では,「メモリカード」がMementoになりますね。このMementoを保持しているのがCareTaker OriginatorはMementoを作ることと,渡されたMementoの状態に自分を戻すことを役割としています。

図15 メモリカードとテレビゲーム(Memento)のクラス図

図15 メモリカードとテレビゲーム(Memento)のクラス図

適用例

このパターンは,アプリケーションにアンドゥ(やり直し)やリドゥ(再実行)の機能を持たせるときによく使われています。

J2SEのjavax.swing.undoパッケージではMementoパターンが用いられています。

表16 例とGoF 本の対応(Memento)

表16 例とGoF 本の対応(Memento)

Observer:日経平均株価の変動を顧客全員へ知らせます

たとえば

A君は某弱小証券会社の新入社員です。デイリーワークとして,日経平均が前日終値の±200円を超えると,先輩から渡された顧客リストをもとに,各々の顧客に株価一覧をFAXするという仕事が与えられていました。顧客リストの追加/削除などのメンテナンスは先輩が随時行います。顧客には個人デイトレーダー,法人企業,新聞記者,大学生,大学教授もいます。A君は全員に同じようにFAXしますが,顧客がそのFAXを何に利用しているのかは知りません。A君はただその時刻の株価を調べ,リストをもとに全員にFAXするだけです。

もし顧客への通知方法が顧客ごとに異なっていたらどうでしょうか。ある顧客には電話,ある顧客にはEメール,そして別の顧客には訪問して口頭で伝える…それだけでA君の1日は終わってしまいそうです。

ある日もう1人の新人B君が入社しました。当然彼にもA君と同じような仕事が与えられました。しかし,顧客リストをもとに顧客へFAXするというところまでは同じですが,日経平均株価ではなく円相場でした。顧客リストもA君とは異なるようです。

しかしよくみると,A君の仕事とB君の仕事には共通部分が多く,整理してマニュアル化しようということになりました。そのマニュアルには顧客へのFAX方法,顧客リストのメンテナンスが記載されています。

パターンの解説

マニュアルとしてまとめた「新人の仕事」自体は仕事をしませんので,インスタンス化できない抽象クラスSubjectとなり,具体的に仕事をする人はそれを継承した具象クラスの「A君の仕事」ConcreteSubjectとなります。

顧客リストはConcreteSubjectのインスタンス変数です。顧客が増えたときには顧客クラスのインスタンスを「顧客を追加するメソッド」で追加します。そして顧客へ通知するメソッドで顧客リストに登録されている顧客全員に株価一覧をFAXします。

ここでConcreteSubjectとConcreteObserverとの関係が1対多になっていることに注意してください。ConcreteObserverが持っている情報(日経平均株価)が変化したとき,ConcreteObserverは全員に対して同じように通知します。ConcreteSubjectは通知を受け取り,何か行動をします。ConcreteSubjectはConcreteObserverがどういう行動をとるのか感知しません。

これがObserverパターンの特徴です。

図16 株価情報伝達(Observer)のクラス図

図16 株価情報伝達(Observer)のクラス図

適用例

J2SEではObserverパターン用のクラスがすでに用意されています。Subjectにはjava.util.Observable,Observerにはjava.util.Observerが対応しています。

表17 例とGoF 本の対応(Observer)

表17 例とGoF 本の対応(Observer)

Prototype:好きな曲を集めたMDを,欲しい人にコピーしてあげる

たとえば

No Music,No Life。俺の人生には音楽は欠かせない。今夜はアイツと湘南の海までドライブ。隣にアイツを乗せてハイウェイで風になる…。アイツのハートをがっちりGETするには俺の魂が入ったMy Best Selection, これしかないぜ。

車内にイカスBGM,窓の外には湘南のさわやかな風と波の音。ロマンチックな雰囲気になるには最高の状況。 「この曲いい曲ね」 「ああ,これはスタン・ゲッソのBODY AND SOUL っていう曲さ。『身も心も捧げているのに,どうして君はわかってくれないの?』っていう意味なんだ。このMDは君をイメージして作ったんだ」

「このMD今度貸して」

「いいよ。今日家に来なよ。コピーしてあげるから」

「うん」

湘南の夜は今日もアツイ。

パターンの解説

世の中の無限と思えるほどの数の曲から,「俺」が作成する可能性のあるMDをあらかじめクラスとして定義することは不可能です。もしクラスで定義できるのであれば,「アイツ」は「俺」にコピーの依頼などせず,クラスから自分でインスタンス化するでしょう(アイツは欲しいCDをCDショップで購入するでしょう)。しかし今回の例では「アイツ」が欲しいのは「俺」がインスタンス化した「My Best Selection」であって,これはCDショップでは購入できません。このようなときPrototypeパターンが有効になります。

Prototypeインタフェース(MD)はCloneメソッド(コピーを作る)を定義していて,ConcreteProduct(MyBestSelection)はそのCloneメソッドを実装しています。そのため,MyBestSelectionのインスタンスはコピーを作成することができます。

PrototypeManagerはConcreteProductを生成すると,それを自分自身のインスタンス変数(MDラック)に保持します。

Clientからの要求があるとそこから該当のインスタンスを取り出し,コピーをして返します。

図17 オリジナルMD(Prototype)のクラス図

図17 オリジナルMD(Prototype)のクラス図

適用例

JavaBeansの生成はPrototypeパターンと言えます。また,グラフィックエディタのカットアンドペーストもPrototypeパターンを使って実装できるかもしれません。ユーザがGUIで選択したオブジェクトを登録し(Ctl+Cでコピーし),それを再利用する(Ctl+Vで貼り付け)とき,そのオブジェクトを1から生成するよりも,コピーして再利用するほうが簡単でしょう。

このように生成されたインスタンスを原型として保持しておき,Clientからの要求があったときに随時コピーして渡す,これがPrototypeパターンです。

表18 例とGoF 本の対応(Prototype)

表18 例とGoF 本の対応(Prototype)

Proxy:あいちゃんの影武者を立てます

たとえば

〇×プロダクションの売れっ子モデルの「あいちゃん」には,毎日さまざまな雑誌,テレビ局,ラジオ局などから出演交渉が来ます。しかし,すべての仕事を受けていたら体がいくつあっても足りません。その対処法として,あいちゃんにそっくりな影武者を立てることにしました。

まず,影武者あいちゃんが依頼人から仕事の出演交渉を受けます。そして仕事についての詳細な情報を聞き,場所や時間などをスケジューリングします。その後,仕事内容が「写真撮影」や「雑誌のインタビュー」など,本物のあいちゃんでなくても影武者あいちゃんで十分受けられる仕事であったらなら,そのまま影武者あいちゃんが仕事をします。しかし,もし仕事内容が「TV出演」「ラジオ出演」「レコーディング」など,本物のあいちゃんでなくてはいけないものであったなら,あいちゃん登場!です。ここで本物のあいちゃんを呼び,それらの仕事をしてもらいます。

このような対策を取れば,受けた依頼のすべてを本物のあいちゃんがしなくても済み,いざというときにあいちゃんを呼んで仕事をしてもらえばよいので,大変効率的といえます。

パターンの解説

Proxyパターンとは,ある仕事を代理で受け,いざ必要というときに本人を呼び出して処理を行うというパターンです。 代理人として仕事を受けるのがProxyです。代理人ではできない仕事を担当するのが実体であるRealSubjectです。たとえ話で言うと,代理人であるProxyが影武者あいちゃんで,仕事を行う本人であるRealSubjectがあいちゃんです。そして仕事を依頼するのがClient,仕事依頼人です。

また,Client(仕事依頼人)から見るとProxy(影武者)もRealSubject(あいちゃん)も,仕事を依頼する人には変わりはありません。これを意識させないようにあるのがSubjectで,ProxyとRealSubjectのインタフェースを定義したものです。たとえ話では,売れっ子モデルの「あいちゃん」です。

Proxyパターンを利用すると,要求があるたびに実体であるRealSubjectを生成しなくてもよくなります。一旦要求をProxyが受け,いざ実際に必要となったときにRealSubjectを生成すればいいのです。そうすることにより,生成のコストを抑えることができます。

図18 あいちゃんの影武者(Proxy)のクラス図

図18 あいちゃんの影武者(Proxy)のクラス図

適用例

JavaではRMIやEJBでProxyパターンが適用されています。

表19 例とGoF 本の対応(Proxy)

表19 例とGoF 本の対応(Proxy)

Singleton:アメリカ合衆国に大統領は1人

たとえば

複数存在すると困るものって,世の中にたくさんあると思いませんか?

国王は1つの国に2人以上いたら争いの種となってしまいますし,1つの会社に社長が複数人いたら,どちらの経営方針に従ってよいかわからず,従業員は戸惑うことになります。彼氏や彼女も何人もいたら後で厄介なことになりますね。アメリカ合衆国に大統領は,1人しか存在しません。これはアメリカ合衆国の憲法や法律などの取り決めによります。

このような取り決めをクラス自体に持たせ,生成されるインスタンスの数を制限するのがSingletonパターンです。

パターンの解説

Singletonクラスは,インスタンス化要求を制御してインスタンスが1つしか作られないことを保証し,またインスタンスにアクセスするための一般的な方法を提供します。このパターンのミソは,クラス自体が自分のインスタンスを管理する役割を持っていることです。クラス図の大統領クラスは大統領インスタンスを保持するための変数がありますね。

Singletonクラスのコンストラクタはprivate宣言しておき,他のクラスからのインスタンス化要求を受け付けないようにします。大統領クラスのコンストラクタである「大統領( )」もprivate宣言されていて,他のクラスから「new大統領( )」なんてすることはできません。また,Singletonクラスのインスタンスを得るには,public宣言してあるstaticメソッドgetInstanceを呼びます。これがSingletonクラスのインスタンスにアクセスするために提供された唯一の方法です。大統領クラスでは「大統領インスタンスを取得する」メソッドがそれにあたります。

getInstanceメソッドが呼ばれると,Singletonクラスは自分自身のインスタンスを保持するstatic変数をチェックし,自分のインスタンスがあればそれを返却し,そうでなければ生成してからstatic変数に代入した上で返します。

大統領クラスでは大統領インスタンスを取得するメソッドが呼ばれると,static変数である大統領をチェックし,大統領がいればそれを返し,大統領がいなければ「new大統領( )」で,大統領のインスタンスを作り,大統領変数に格納した後返します。

図19 大統領(Singleton)のクラス図

図19 大統領(Singleton)のクラス図

適用例

アプリケーションの世界では,印刷ジョブを管理するプリンタスプーラやファイルシステム,ウィンドウマネージャ,デバイスドライバなどがSingletonパターンで設計されることが多いようです。

J2SEではjava.lang.Runtimeクラスで,Runtimeオブジェクトの取得にSingletonパターンを適用しています。

表20 例とGoF 本の対応(Singleton)

表20 例とGoF 本の対応(Singleton)

State:エスカレータの状態にあわせて動作を変えます

たとえば

X ビルサービスのエスカレータは,そのときどきの状態によって動作を変えることができます。エスカレータの状態は管理センターで随時管理しており,状態としては「運転状態」(エスカレータが動いている)と「停止状態」(エスカレータが停止している)があります。

エスカレータの開始地点にはセンサーが付いていて,センサーの前を人が通過すると管理センターに信号が届くようになっています。また管理センターには停止スイッチがついていて,エスカレータを停止することができます。このエスカレータの状態をステートチャート図で表すと図20-1のようになります。A~Dの動作を繰り返してエスカレータは毎日動いています。

つまり,管理センターに「センサー反応」や「スイッチ切」という命令が届くと,「運転状態」「停止状態」という各状態にあわせてエスカレータの動作が変わるわけです。このように複雑な状態遷移を容易に表現するものとしてStateパターンがあります。

図20-1 エスカレータ(State)のステート図

図20-1 エスカレータ(State)のステート図

図20-2 ●エスカレータ(State)のクラス図

図20-2 ●エスカレータ(State)のクラス図

パターンの解説

Stateパターンとは,ときどきの状態にあわせて動作の内容を変えることができるパターンです。 状態を表すクラスをStateといい,状態における動作のインタフェースを定義します。そして具体的な状態を表すクラスをConcreteStateといい,Stateで定義された動作の内容を実装します。先のたとえでは,Stateがエスカレータで,ConcreteStateが「運転状態」「停止状態」という各状態です。その中で定義された動作が「センサー反応」や「スイッチ切」などのメソッドです。

また,状態に対して動作を呼ぶ役目がContextで,自分の動作であるConcreteStateを保持しています。たとえ話でいうと,Contextが管理センターで,そのときの状態を保持しています。 あるものの状態をクラスで表現し,各状態の動作をカプセル化するのがStateパターンです。

Contextである管理センターに振る舞いを記述し,「運転状態」だったらこうで,「停止状態」だったらこうで…と条件を分岐させて複雑なコードを書かなくても,ConcreteStateに動作を記述してContextで保持しておけば,管理も保守も簡単になります。

適用例

JavaAPIでは利用されていないようですが,さまざまな状態に合わせた処理などに応用できます。

表21 例とGoF 本の対応(State)

表21 例とGoF 本の対応(State)

Strategy:状況に応じてお金を手に入れる戦略を選びます

たとえば

「あっ!そう言えば,今日デートの約束だっけ。フランス料理を食べにいくんだよな…」とあなたは不安そうに自分の財布を開きます。しかし案の定,あなたの財布には千円札が数枚見えるだけ。今日は給料日の前日です。確か銀行の預金は数千円しかないはず。

「げげっ!そうだ,Aに借りよう…」と親友である(はずの)A君に相談します。「は?おめぇ何言ってんだ。それより,これまでに貸した10万返してくれよ!」と,逆に詰めよられるはめに。 トボトボ駅前に向かいました。すると道端のお姉さんが「よろしくお願いします~」とポケットティッシュを1つくれました。で,何気なくティッシュの裏を見ると,そこには「アコモ」という文字が。「そうだぁぁ!この手があったか」とあなたは消費者金融のATMに向かって走り出しました…。

このような共通の目的(お金を手にいれる)に対して,それを実現する戦略を複数用意しておき,その場の状況に応じて適切な戦略を選ぶというデザインパターンに,Strategyパターンがあります。

パターンの解説

たとえ話では,あなたの目的(=解決すべき問題)は「お金を手に入れる」ことでした。また,この目的のための具体的な戦略としては「銀行の預金からお金をおろす」「友達から借金する」「消費者金融のATMでキャッシングする」の3つがありました。Strategyパターンではまず,この「お金を手に入れる」という目的を,Strategyインタフェースのメソッドとして定義します。その上で,上記の具体的な戦略を,StrategyインタフェースのサブクラスであるConcreteStrategyクラスに個別に実装します。Client(あなた)は,状況を判断して適切なConcreteStrategy(「銀行から下ろす」や「親友に借りる」)をインスタンス化し,Contextクラス(「お金が必要な状況」)を通してその戦略を実行します。ちなみに,ContextクラスはConcreteStrategyに処理を委譲する形で結びついています。

このようにすると,新たなお金を手に入れる戦略(「親から借りる」など)が追加されても,ConcreteStrategyクラスを1つ追加すれば対応できます。さらに,ClientはContextが委譲しているConcreteStrategyのインスタンスを動的に入れ替えることで,たとえ話のようにその場の状況に応じてつぎつぎと戦略を切り替えるということも実現できます。

図21 お金を手に入れる戦略(Strategy)クラス図

図21 お金を手に入れる戦略(Strategy)クラス図

適用例

J2SEでは,java.awt.LayoutManagerクラスでStrategyパターンを適用しています。

表22 例とGoF 本の対応(Strategy)

表22 例とGoF 本の対応(Strategy)

Template Method:ハンバーガに具をはさむ手順を個別に定義します

たとえば

ハンバーガショップMではハンバーガの種類ごとに調理手順書があり,チーズバーガとBLTバーガにはそれぞれ別の調理手順書が使用されていました。店長Aさんは,新商品が登場するたびに○○バーガ調理手順書を作成していました。しかし,毎回新しく作成するのは時間がかかって面倒です。そんなある日店長Aさんは,チーズバーガとBLTバーガの調理手順書を見比べて,ほとんど同じ手順だということがわかったのです。

ハンバーガを調理するにはいくつかの工程がありますが,その中でハンバーグをこねたり焼いたりという工程の手順はどんなハンバーガでも変わらなかったのです。ただ1点,パンに具をはさむ工程の手順だけがハンバーガごとに変わることがわかりました。

Aさんはいろいろと考えた末に,ハンバーガの調理手順書を共通化しました。これがハンバーガ調理共通手順書です。この手順書を参照してすべてのハンバーガを調理します。しかしパンに具をはさむ工程の手順だけはハンバーガごとに異なるため,その個所には「パンに具をはさむ工程は各ハンバーガの手順書を参照せよ」と注意書きしておきました。こうしてチーズバーガ調理手順書やBLTバーガ調理手順書には,パンに具をはさむ工程の手順だけを詳細に書いたのです。

パターンの解説

ハンバーガ調理共通手順書は,AbstractClassです。AbstractClassではテンプレートメソッドを実装します。たとえ話では「ハンバーガを調理する」があてはまります。またAbstractClassでは,ConcreteClassで実装する必要のある抽象メソッドを定義します。これは「パンに具をはさむ」です。チーズバーガ調理手順書は,ConcreteClassです。AbstractClassで定義された抽象メソッド,つまり「パンに具をはさむ」を実装します。

TemplateMethodパターンはアルゴリズムの不変な部分をスーパークラスで実装し,変わりうる部分をサブクラスで実装するパターンです。

この例では,それぞれのサブクラス(チーズバーガ調理手順書やBLTバーガ調理手順書)で共通となる部分(ハンバーグをこねる,ハンバーグを焼くなど)を抜き出し,スーパークラス(ハンバーガ調理共通手順書)に実装しました。 その上で,個別な部分(パンに具をはさむ)をサブクラスに記述できるようにしたのです。

TemplateMethodパターンを使用することにより,ハンバーガ調理手順を効果的に再利用できました。

図22 ハンバーガー調理(Template Method)のクラス図

図22 ハンバーガー調理(Template Method)のクラス図

適用例

J2EEでは,javax.servlet.http.HttpServletクラスがTemplateMethodパターンを適用しています。また,第3章の事例でも取り上げていますので参照ください。

表23 例とGoF本の対応(Template Method)

表23 例とGoF本の対応(Template Method)

Visitor:会社の全社員を回って旅行の集金をします

たとえば

ある会社の総務のAさんは社員旅行の集金をする係なので,毎月社員全員の席を回ってお金を集めています。社員とは,社長からツリー構造で成り立っている社員名簿の全員を指します。

一方Bさんは,給与明細を配って印鑑をもらう仕事をしています。しかしAさんもBさんも,どのような社員がいるかということは気にしていません。社長が持つツリー構造の社員名簿通りに,社員を回るだけです。社員の増減や人事異動があったとしても社員名簿が変わるだけで,AさんとBさんの動作は変わりません。また,保険証を全員に配って回る仕事をするCさんが加わっても,社員名簿の中身を理解しなくてもOKです。

パターンの解説

Visitorパターンとは,操作する対象の構造と実際の操作を分離するパターンです。ある操作をするために対象の構造の中を渡り歩くものをVisitor(社員を回る人)といい,その操作である「(社員を)訪れる」を宣言します。

Visitorを実装したものをConcreteVisitor(AさんやBさん)といい,操作(社員を訪れる)の処理の中身を実装します。また,Visitorが訪れる構造の要素をElement(社員名簿)といい,Visitorを受け入れるためのインタフェースを定義します。Elementを実装したものはConcreteElement(一般社員と役員),Elementの構造を操作できるものがObjectStructure(社長),操作の依頼者がClient(社長)です。

Client(社長)は社員名簿のツリー構造を保持しており,社員名簿に対する操作をAさんやBさんに依頼します(Visitorの受入れ)。 ConcreteElementのVisitorを受け入れるための操作は,引数にVisitorを受け取り,受け取ったVisitorに対して操作(訪問する)を呼び出します。そして,Visitorの中の操作(訪問する)は引数にConcreteElementを受け取り,受け取ったConcreteElementに対して実際の処理を行います。図23のように(Javaなどの)thisで自分自身を引数で渡し,お互いがお互いを呼び出して処理を行います。

呼び出しの構造はとても複雑ですが,ElementとVisitorがお互いを呼び出すことにより,構造の部分をElementへ,操作の部分をVisitorへ分離することができます。 こうすることにより,新しい操作(Visitor)を追加したり,変更したりすることが容易になります。

図23 旅行の集金(Visitor)のクラス図

図23 旅行の集金(Visitor)のクラス図

適用例

JavaAPIでは使用されていないようですが,さまざまな場面で利用できます。

表24 例とGoF 本の対応(Visitor)

表24 例とGoF 本の対応(Visitor)

  • 注1:特集4第3章も参照ください。
  • 注2:この例の場合,Invokerに命令を書いた紙が貼り付けられているので,履歴の管理もされています。

お問い合わせ

ウルシステムズに関するお問い合わせは以下のページからどうぞ。