GoFデザインパターンの生成パターンを会得しているかどうかを示す、な話 振る舞いに関するパターン篇
2021.2.3 追記
Template Methodの例示が正確にイメージしづらく、理解を妨げやすいものだったため、内容を正確なものに修正いたしました。
ごあいさつ
こんばんわ、初音ミク×KODOの2020年バージョンが中止になったという発表を見て打ち拉がれて、今日の更新をどれだけ休もうかと思った著者です。
就職活動中の身で収入ナシの私としてはGoFデザインパターンの理解深化を止めるわけには行かないので、重い腰を上げることにしました。
というわけで、昨日まで、GoFデザインパターンのうち、「生成に関するパターン」と「構造に関するパターン」を紹介してまいりました。
それでは、最後のまとまりである「振る舞いに関するパターン」の説明をしていきます。今現在のメンタルがメンタルですので、簡潔に説明できればと思います(それが一番むずかしいねんけど…)。
1. Chain of Responsibility
簡単に言うと「責任のたらい回し」、たくさんあるオブジェクト同士を鎖でつなぎ、何か現象が発生するときっかけとなる条件を元にオブジェクトを辿り、条件に合致するオブジェクトが責任を持って処理するパターン。
具体例としては、C#のdelegator(イベントハンドラとか)がイメージしやすいと思います。イベントオブジェクトを渡り歩いて、条件が合致する処理を実行、一通り渡り歩いたり、「ここで打ち止め」の合図があったらイベントハンドラを打ち切るかたちです。
2. Command
システム全体の中で処理する命令(メソッド形式)ではなく、命令そのものをクラスとして設計する方法。命令の概要を抽象化しておいて適応範囲の幅を広げておくとより便利に。
具体例としては、履歴処理を設計するときによく使われます。履歴用のキューに命令を格納する形をとるのがイメージしやすいですね。
3. Interpreter
処理を別ファイルとして準備して、システム内ではファイルの中身を翻訳して実行する設計を行うことです。
具体例としては、プログラミング言語処理のインタプリタが挙げられます(内部の処理は千差万別ですが…)。
Unityで例えると、ノベルゲームのシナリオ処理がイメージしやすいと思います(宴というノベルゲームアセットでは、Excelファイルをシナリオとして実装できるように設計されています)。
4. Iterator
これはもう色んな所で実例があるので非常にイメージしやすいパターンですね。インデックスを順に参照したりするなど、まとまった処理を繰り返し実行できるように設計します。
お歳暮クラスのオブジェクトが多数あり、お歳暮をリスト化して配送する処理を繰り返し手配するような場合に有効です。
Rubyのdo...endやPythonのmap関数、C#のforeachがそれに当たります。
5. Meditator
ワーカーに相当するオブジェクトに個別に処理を指示せず、間に窓口担当(相談役)に相当するオブジェクトを用意して、代わりに指示してもらいます。SOLIDのインタフェース分離の法則や単一責任の法則に倣った形と見ていいと思います。
Iteratorで紹介したイメージ例をベースにすると、「お歳暮」だけではなくて「お中元」も手配したいときは、それぞれを抽象化するインタフェースを準備した上で「手配担当者」クラスを別に作り、お歳暮とお中元の手配を全部任せるように設計します。
実例は思い出せませんが、オブジェクトプールを管理するオブジェクトがMeditatorパターンに相応します(ゲームを開発するときの弾幕を設計するときをイメージすると通りやすくなると思います)。ComponentやIterator、Flyweightと組み合わせる場面が多いと想像できます。
6. Memento
オブジェクトの状態をスナップショットとして記録する際に使用するパターンです。無計画に設計するとSOLIDのOpen-Closedパターンやカプセル化に反する内容になってしまいますが、内部でスナップショットを保存するためのクラスを設計しておき、外部からはそのクラスのインスタンス内でしかアクセスできないようにします。
ツールなどのUndo、Redo機能やファミコンミニなどのレトロゲーム移植でよく使われる「どこでもセーブ」機能イメージすると理解が進みます。
7. Observer
各オブジェクトの状態を監視しておき、変化が起きればObserverに通知が渡り、指定した処理を実行します。
Observerと監視対象の間は疎結合であり、Observerは監視対象の詳細をよく知りませんし、監視対象もObserverが何かもよく知りません。そのため、お互いに依存しない処理を設計できます。依存性逆転の原則やリスコフの置換原則を実現するのに使えます。
分散処理を実装する上で非常に便利な方式です。
Pub/Subという言葉にピンと来る方はいらっしゃるでしょうか。PublisherはObserver、Subscriberは監視対象と置き換えれば納得していただけるでしょうか。
Observer(Pub/Sub)を使用している具体例として、Facebookの設計思想・FLUXやそれをベースに設計したReact、メッセージキュー方式の非同期処理処理システム(スマートフォンの通知も考えられますね)、AWS SNS、GCP Cloud Pub/Subがあります。
8. State
各種状態をクラスとして設計する方式です。状態だけではなく、状態によって変わる情報をまとめるときに最適なパターンです。
Observerパターンと組み合わせると非常に便利です。
実例としては、やはりFLUXとReactに突きますね。
9. Strategy
Strategyを和訳すると「戦略」です。コンピュータの世界で言えば処理方法・アルゴリズムが戦略の一例と取られます。
このパターンは、その戦略である処理方法やアルゴリズムをごっそり入れ替えられるように設計することです。インタフェース分離原則やOpen-Closedの原則、依存性逆転原則を守る際に使えます。
魚料理をする処理を設計する場合、様々な料理方法を各種オブジェクトとして準備する(刺し身手順クラスとかマリネ手順クラスとか)とイメージしやすいでしょうか。
実例としてイメージしやすいのは暗号化アルゴリズムの適応ですね。SHA1やSHA2、RSAなどのアルゴリズムをオブジェクトとして用意して、指定の暗号化でメールを送信したりレスポンスを返す処理がいろいろなWebフレームワークやライブラリで見受けられます。
10. Template Method
とある処理(メソッド)の中身を基底クラスで予め決めておいて、個別の処理が必要そうな箇所ではサブクラスに丸投げする方式。修正する場合も、大まかな部分なのか詳細な部分なのかで修正箇所を切り分けることが出来ます。SOLIDでは単一責任の法則・依存性交換の法則を守りやすくなります。
例えば、「工作」クラスで作業手順を設計する際、「手順実行」メソッド内で行う数多の作業(メソッド)はとりあえずの作業として抽象化します。
一例として、「板と板をくっつける」作業がある場合、「工作」ではその箇所ではとりあえず「板と板をくっつける」とだけしておいて抽象化し、「釘を使う工作」クラスでは釘を使った作業を、「接着剤を使う工作」クラスでは接着剤を使った作業ができるように設計するようなものです。
11. Visitor
そもそもはデータと処理を分離させるように設計したいときに使えます。Visitorには処理本体(たいていvisitという名前で定義しています)を用意しておき、データオブジェクトがそのVisitorを受け入れる(たいていacceptという名前のメソッドを定義しています)処理(基本的にVisitorのvisitメソッドを呼び出すだけです)を準備しておくことで、データと処理の疎結合を遵守できます。それぞれの多様性を保持やSOLIDの遵守に有効です。
ファイルのディレクトリ構成をイメージするとわかりますが、ファイルとディレクトリが一つ一つのデータオブジェクトとしてある場合、
ぱっと見、Strategyパターンに似ているように見えます(アルゴリズムオブジェクトをVisitorオブジェクトに見立てられるように見えます)が、設計思想が違う(Strategyはあくまでも処理をオブジェクトとして管理できるようにするだけ)のでVisiorパターンにも価値があります。
ちょっと視点を替えて実装の話になりますが、Visitorパターンを考慮する際、「ダブルディスパッチ」という言葉を頭の片隅に置くといいと思います。
C++、C#、Javaではオーバーロードしているメソッドから使用したいメソッドを選ぶときや、基底クラス・インタフェースからサブクラス・実クラスを見つけ出すことをディスパッチと言います。
Visitorパターンで説明すると、Vistor側が何者かを調べるディスパッチ、データ側が何者かを調べるディスパッチ、計2回のディスパッチ(=ダブルディスパッチ)で処理内容が決まることから、このパターンはダブルディスパッチを考慮に入れることで実装しやすくなります。
さいごに
さて、振る舞いに関するパターンをひと通り見てきました。
デザインパターンを全体的に見て、実装者にわかりやすい設計を突き詰める際の共通言語として使えるように整理されていると感じました。実際のシステムはもっと複雑で一言で済ませられませんが、そうなるとなおさらコミニュケーションに気をつけることになります。できる限り単純な構造や振る舞いの組み合わせに落として、手戻りの必要がない程度に明確な説明できるように精進していきたいと思います。
長らくのご清聴ありがとうございました。
この記事が参加している募集
この記事が気に入ったらサポートをしてみませんか?