【デザインパターン】デザインパターンの振り返り【後篇】
どうも、こんばんわ。
世の中は先日までクリスマスムードだったようですね。
そんな、自分は今日も研究室でPCの前に座っています。
今年は色々な事があったけど、来年から新天地でも頑張って行きたいです!
概要
今回は前篇(【デザインパターン】デザインパターンの振り返り【前篇】 - datchの日記)に引き続きCommandパターンから始めて行きたいと思います。
お気づきの方も居ると思いますが、じつは振り返りはCompoundパターン(参考書でそう命名されているだけで実際にそんなパターン名の物はない点に注意)をやる前の事前学習でも似たような事をやっています。
しかし、そこで触れていないパターン(FactoryMethod, Singleton, Command, Facade, Template Method, State)もあり、忘れてしまっているパターンもあったため再度の振り返りとなります。
Command
Factoryはインスタンス生成の一元管理だったが、Commandパターンは特定の処理を一元管理するパターンです。
また、処理をinterfaceでカプセル化することが出来るので、依存性を下げることで変更に頑健なコードにすることが出来ます。
Commandパターンには処理本体を所持するCommandオブジェクトと、Commandオブジェクトを保持して処理を実行するInvoker、Commandオブジェクトからの処理の打診を実際に行うReceiver、Commandオブジェクトを利用するClientという4つのオブジェクトに分けられます。
では、以下にコードを記述します。
// Command interface Command { public void execute(); } /** * Concrete Command * 照明の点灯を行う */ class LightOnCommand implements Command { Light light; public LightOnCommand(Light light) { this.light = light; } public void execute() { light.on(); // もちろん照明の点灯以外にも合わせて何か違う処理を行っても良い } } /** * Concrete Command * 照明の消灯を行う */ class LightOffCommand implements Command { Light light; public LightOffCommand(Light light) { this.light = light; } public void execute() { light.off(); } } // Receiver class Light { public void on() { // 照明の点灯 } public void off() { // 照明の消灯 } } // Client class RemoteControl { Command onCommand; Command offCommand; public RemoteControl() { } public void onButtonWasPushed() { onCommand.execute(); } public void offButtonWasPushed() { offCommand.execute(); } public void setOnCommand(Command onCommand) { this.onCommand = onCommand; } public void setOffCommand(Command offCommand) { this.offCommand = offCommand; } } // public static int main とクラス定義を省略 { RemoteControl remoteControl = new RemoteControl(); Light livingRoomLight = new Light("リビングルーム"); Light kitchenLight = new Light("キッチン"); LightOnCommand livingRoomLightOnCommand = new LightOnCommand(livingRoomLight); LightOnCommand livingRoomLightOffCommand = new LightOffCommand(livingRoomLight); LightOnCommand kitchenLightOnCommand = new LightOnCommand(kitchenRoomLight); LightOnCommand kitchenLightOffCommand = new LightOffCommand(kitchengRoomLight); remoteControl.setOnCommand(livingRoomLightOnCommand); remoteControl.setOffCommand(livingRoomLightOffCommand); remoteControl.onButtonWasPushed(); remoteControl.offButtonWasPushed(); // こんなに風にコマンドの変更で処理を切り替えることも出来る remoteControl.setOnCommand(kitchenLightOnCommand); remoteControl.setOffCommand(kitchenLightOffCommand); remoteControl.onButtonWasPushed(); remoteControl.offButtonWasPushed(); }
この他にもCommandパターンはundo機能の実装も行う事も多く、それらの機能は動作のログを取得したり、一つ前の状態を保持して前に戻すという手段も取ったりする。
Adaptor
このパターンは本来、違う派生のオブジェクトを同じものとして扱う事が出来るようになるパターンです。
今回はエビワラーとサワムラーというパンチと蹴りというまったく違うメソッドを同じものとして呼び出してみましょう*1
interface Ebiwara_ { public void punch(); } class GreenEbiwara_ implements Ebiwara_ { public void punch() { // メガトンパンチ! } } interface Sawamura_ { public void kick(); } class NormalSawamura_ implements Sawamura_ { public void kick() { // メガトンキック! } } // サワムラーをエビワラーとして扱えるAdapterオブジェクト class Sawamura_Adapter implements Ebiwara_ { Sawamura_ sawamura_; public Sawamura_Adapter(Sawamura_ sawamura_) { this.sawamura_ = sawamura_; } public void punch() { sawamura_.kick(); } } // public static int main とクラス定義を省略 { Ebiwara_ ebiwara_ = new GreenEbiwara_(); // サワムラーをエビワラーとして見せるAdapterオブジェクトを生成 Sawamura_ sawamura_ = new Sawamura_; Sawamura_Adapter adapter = new Sawamura_Adapter(sawamura_); }
こうすることで本来、サワムラーだったはずなのにエビワラーのように見せて攻撃することが出来るようになりました。
これじゃ、意味が無いと感じる方も居ると思うのですが、後ほど紹介するCompositeパターンでは一つのクラスしか扱えないのですが、特例としてAdapterを使うことで同様に扱うことが出来るようになります。
ただ、特例としての利用なのでこれを大量に使うようになった場合はそもそも設計からやり直す必要がある場合が殆どだと思いますが(間違った見解だったらご指摘していただけると助かります)。
Facade
ファサードパターンは複雑に絡み合ったサブシステムにアクセスしやすいインターフェース(関数)を提供するものです。
一番想像しやすいのはAPIですね。
APIはユーザが欲しい情報や行って欲しい処理を関数越しに指定するだけ、その恩恵を享受することが出来るような作りになっています。
Facadeパターンはある程度経歴の長いプログラマーであれば意識せずとも似たような形式の物を利用していることが多いパターン。
これについては非常に概念的なのでコードは書きませんが、Commandパターンとどのような違いがあるかなどをまとめているカナダのスライドがあったのでそちらのURLを載せておきます。
https://cs.uwaterloo.ca/~a78khan/cs446/lectures/2011_06-jun_10_ServiceLayer.pdf
Commandパターンはテストのしやすさや入れ子での定義が可能などの用途の広さ。
Facadeはアプリケーション層でのユーザの見える部分の機能を提供する時などに用いる例を出していますね。
Commandは開発者寄りのパターン、Facadeはユーザ寄り(アプリケーションを利用する開発者も含む)のパターンというイメージで自分は理解しました。
TemplateMethod
TemplateMethodは一連の処理の流れが決まっているが、その流れでどのような処理を行うかは具体的に決めるのはサブクラスというパターンです。
教科書ではコーヒーと紅茶の以下の4つの工程に注目して解説していました。
- お湯を沸かす
- 淹れる
- カップに注ぐ
- 香辛料を加える
これらの一連の流れはコーヒーも紅茶も概念的に同じです。
しかし、お湯を沸かす・カップに注ぐという工程はまったく同じですが、淹れる・香辛料を加える、という動作にかんしては違います。
コーヒーは85~90度のお湯で数十秒の間蒸らした後にお湯を注いていきます。*2
紅茶は沸騰したての100度に近い温度でお湯を注ぐことで一気に茶葉が広がることで香りが引き立ちます。
これらの工程は違うがところどころの詳細が異なる時にTemplateMethodは力を発揮します。
以下にコーヒーと紅茶のコードを載せていきましょう。
abstract class CaffeineBeverage { // finalにすることで一連の工程を変更不可能にする final void prepareRecipe() { boilWarter(); brew(); pourInCup(); addCondiments(); } abstract void brew(); abstract void addCondiments(); public void boilWarter() { // お湯を沸かします } public void pourInCup() { // カップに注ぎます } } class Coffee extends CaffeineBeverage { public void brew() { // お湯は85~90度で(以下略 } public void addCondiments() { // ミルクと砂糖 } } class Tea extends CaffeineBeverage { public void brew() { // 紅茶を95度のお湯で浸し(以下略 } public void addCondiments() { // レモンを追加 } }
Iterator & Composite
いきなり2つのパターンが出てきて驚いた方も居ると思いますが、CompositeがIteratorを利用するので同時に紹介したいと思います。
Iteratorは色々な言語で出てくるので聞いた方も居ると思いますが、単純に繰り返し処理を一般化したパターンです。
配列に対してfor文を回して全ての要素に対して同一の処理を行うことは、プログラマとしては野球選手がキャッチボールをするように当たり前のように出てくる処理で頻出であり基礎です。
そして、Compositeはn分木構造のデータ構造をIteratorを使いながら処理を行うパターンです。
Compositeは木構造でいう葉の部分をItem、内部ノードに当たる部分をComposite、そしてそのどちらの機能もまとめて定義しているのがComponentという構造になっています。
Compositeは自由にItemやCompositeを追加して木構造を作ることが可能です。
尚、Iteratorはforeach関数の出現によって不要だという主張をする人も居るので頭の片隅にとどめておくだけでも良いでしょう。*3
State
Stateパターンは状態遷移図が記述できるような有限オートマトンで活躍することが出来ます。
現在の状態毎に他の状態に対してのアクションを定義することが出来るため、処理の切り分けが行い易くなります。
それでは道端でよくみる自動販売機のオートマトンを元に作成していきましょう。
interface State { public void insertCoin(int value); public void ejectCoin(); public void purchase(); } // 商品が無い状態 // もちろん、補充がされるまでは何も出来ない public class EmptyState implements State { public Dispenser dispenser; // 以降、全て同一の処理なのでコンストラクタは省略 public EmptyState(Dispenser dispenser) { this.dispenser = dispenser; // 状態を保持するオブジェクトを自身も保持する } public void insertCoin(int value) { // お金を受け付けない } public void ejectCoin() { // 何も処理を行わない } public void purchase(int id) { // 何も処理を行わない } } // お金が投入されていない状態 public class NoneInsertedState implements State { public Dispenser dispenser; private int amount = 0; public void insertCoin(int value) { amount += value; if(amount > 120) { dispneser.setState(dispenser.getEnoughtState(amount)); } else { dispneser.setState(dispenser.getNotEnoughtState(amount)); } } public void ejectCoin() { // 何も処理を行わない } public void purchase() { // 何も処理を行わない } } // お金が足りない状態 public class NotEnoughState implements State { public Dispenser dispenser; private int amount; public void insertCoin(int value) { amount += value; if(amount > 120) { dispneser.setState(dispenser.getEnoughState()); } } public void ejectCoin() { dispenser.setState(dispenser.getNoneInsertedState()); } public void purchase() { // 何も処理を行わない } } // お金が足りない状態 public class EnoughState implements State { public Dispenser dispenser; private int amount; public void insertCoin(int value) { amount += value; if(amount > 120) { dispneser.setState(dispenser.getEnoughState()); } } public void ejectCoin() { dispenser.setState(dispenser.getNoneInsertedState()); } public void purchase() { System.out.println("商品が出ました!"); amount -= 120; this->ejectCoin(); // 合わせてここに商品があるかどうかの処理を記述して、EmptyStateに持ってくることも考えられる } }
おわりに
最後あたりは時間が遅くなってきたため駆け足で書いた感がありますね(^^ゞ
非常に長い期間を取ったデザインパターンでしたが、これでデザインパターンをブログのネタにするのは一旦終了にしたいと思います。
以前からいずれはデザインパターンについて触りたいと思っていましたが、やはり手法を知ることで見聞が広まるのは非常に良いことですし、自身も「如何に良いコードを書くか」という点については貪欲に追求したいと考えています。
もちろん、プログラム言語自体の記述力の低さによりデザインパターンが生まれたのが原因なので、革命が起きた時にはこのような通例の書き方も消えてしまうのかもしれませんが、それはもう少し先の話でしょう。
以前から記事にしようと考えていたセキュリティについては別の場面で触ることになったので、次回からはこちらの本を参考にして記事を書いていきたいと思います。
やはり自分は基礎から積み上げていくのが大事だと思う身で、サーバサイドに関して知らなすぎるのでこれを機会に勉強したい所存です!
長い間ではありましたが、どうかこれからもよろしくお願い致します(`・ω・´)ゞ