【デザインパターン】テンプレートメソッドパターン
だっちだ、明日は実家に帰省するのだ!
最近、やはり研究に対するモチベーションの低さをどうしようか、と頭を悩ませています。
企業共同研究の成果は学会に出す雰囲気がまったく無いし、研究室内での共同研究で提案されたアルゴリズムの計算時間的*1に絶望的すぎる。
私は何のために研究をしているのだろう?
さて、今回は「TemplateMethodパターン」について説明していくよ。
TemplateMethodパターンって何ですか?
Templateメソッドパターンは、おそらくOOP(オブジェクト指向プログラミング)を習う上で、意識はせずともやっているパターンだろう。
OOPの三本柱である多態性を習うときに、最初に身近な物(車や飲み物、食べ物など)を汎化し、それを継承して特化させる仮定で抽象関数を使うだろう。
車では"走る"という動作は"アクセルを踏む"で共通だが、"エンジンをスタートする"という動作は車によって違う。
それと同様に、"車を動かす"という一連の流れは、一部は同じだが他は違うという場合がある。
共通な部分は汎化されたクラスにまとめ、継承で車種によって異なる動作毎に抽象関数で定義する。
一連の流れを1つの関数にまとめ、抽象関数によって一部の動作を差し替え可能にしたのがTemplateMethod関数だ。
Wikipediaさんも参照。
Template Method パターン(テンプレート・メソッド・パターン)とは、GoF(Gang of Four; 4人のギャングたち)によって定義されたデザインパターンの1つである。「振る舞いに関するパターン」に属する。Template Method パターンの目的は、ある処理のおおまかなアルゴリズムをあらかじめ決めておいて、そのアルゴリズムの具体的な設計をサブクラスに任せることである。そのため、システムのフレームワークを構築するための手段としてよく活用される。
具体的な例
さて、ここではいつもと同じように参考書から身近な例を引用します。
以前の章で題材にされていたスターバズコーヒーの飲み物に関してです。
スターバズコーヒーのバリスタトレーニングマニュアル
バリスタのみなさん!スターバスの飲み物を用意する際は、これらのレシピに正確に従ってください。
- スターバスのコーヒーのレシピ
(1) お湯を沸かします。
(2) 沸騰したお湯でコーヒーを淹れます。
(3) カップにコーヒーを注ぎます。
(4) 砂糖とミルクを加えます。
- スターバズの紅茶のレシピ
(1) お湯を沸かします。
(2) 沸騰したお湯に紅茶を浸します。
(3) カップに紅茶を注ぎます。
(4) レモンを加えます。
さて、ここで飲み物の一連の作り方を提示していますが、コーヒーと紅茶のレシピについてそれぞれ比較してみましょう。
(1)「お湯を沸かす」という動作は、まったく一緒ですね。
(2)「沸騰したお湯」という点は一緒です。それを「淹れる」か、「浸す」かの違いしか持っていません。
(3)「カップに注ぐ」という動作は、まったく一緒ですね。
(4)「なにかトッピングを行う」という動作になってます。
既に(1)と(3)の処理に関しては、まったくの共通の処理であり、ベースクラスに定義するという形で問題ないでしょう。
そして、(2)と(4)に関しては作る飲み物によって動作が変わるので、こちらは派生クラスで定義する形を取ります。
さぁ、それではベースクラスと紅茶、コーヒーの派生クラスを実際にTemplateMethod関数を用いながら作成して行きましょう。
public abstract class CaffeineBeverage { final void prepareRecipe() { boilWater(); brew(); pourInCup(); addondiments(); } abstract void brew(); abstract void addCondiments(); void boilWater() { System.out.println("お湯を沸かします"); } void pourInCup() { System.out.println("カップに注ぎます"); } }; public class Tea extends CaffeineBeverage { public void brew() { System.out.println("紅茶を浸します"); } public void addCondiments() { System.out.println("レモンを追加します"); } }; public class Coffee extends CaffeineBeverage { public void brew() { System.out.println("紅茶を浸します"); } public void addCondiments() { System.out.println("レモンを追加します"); } };
このように一連の流れで異なる動作をする部分だけを抽象メソッドとして定義し、継承して関数として定義するのがテンプレートメソッドです。
コードの重複を削除し、宣言する型によって動作を切り替えることが可能になりました。
フック
さて、ちょっとした豆知識ではありますが「フック」という言葉を聞いたことはありますか?
よくフレームワークのリファレンスなどで目にする方も多いかと思います。
では、「フック」とは何なのか?
デフォルトで何もしない具象メソッドの事をフックと呼びます。
とりあえず、ベースクラスには宣言したけど、どのクラスでも使うような機能でない、オプション的な役割の動作を定義する関数に用いたりします。
先ほどのクラスでトッピングを追加する、という>>addCondiments()<<という関数がありましたが、この処理はどの飲み物でもあてはまる訳ではないのはわかりますよね?
なぜなら、トッピングを施したくない客も存在するからです。
なので、このように定義してトッピングをするかしないかというのをフックを使って管理します。
public abstract class CaffeineBeverageWithHook { final void prepareRecipe() { boilWater(); brew(); pourInCup(); if(customerWantsCondiments()) { addCondiments(); } } abstract void brew(); abstract void addCondiments(); void boilWater() { System.out.println("お湯を沸かします"); } void pourInCup() { System.out.println("カップに注ぎます"); } // この関数が「フック」です // サブクラスはオーバーライドすることが出来ますが、オーバーライドをしなければいけない訳ではないですね boolean customerWantsCondiments() { return true; } };
>>customerWantsCondiments<<という関数では、派生クラスで顧客にユーザの入力によって>>true<<か>>false<<に返り値を変化させるような関数を用意します。
このような感じで、とりあえずフックされた関数は定義しなくても正常に動作し、継承先で自由に独自の処理を追加出来る関数になります。
フレームワークなどでも、クラスやヘルパーの関数を「フックする」というのはこういう関数を変更する、ということなのですね。
最後に
フックという言葉はよく聞いていたいのですが、こういう関数がフックとは知らなかったので良い勉強になりました。
以前のFacadeと合わせて、抽象度が高く、他のデザインパターンでも似たような特性が見受けられるパターンです。
TemplateMethodパターンも今までのデザインパターンにも深く関与しているので、しっかりと理解した方が良いですね。
*1: O(10^15)