メインコンテンツまでスキップ

デザインパターン

簡単に言うとどっかのソフトウェア開発者が設計ノウハウをパターンとしてまとめたもの。
用語が初めて発表されたのは、GoF(Gang of Four:イカした4人組という意味)と呼ばれる4人の開発者が23のパターンをまとめた「オブジェクト指向における再利用のためのデザインパターン」という書籍から。

初版が1994年



C 言語: 1972年
C++ : 1980年代
Java : 1995年
C# : 2000年

昔?最近?に発表されたパターンが現在でもしばしば使われているほど、 汎用性の高い設計パターンになります。
テクニックや概念のようなものになります。

書籍の名前にもある通り、GoFのデザインパターンは オブジェクト指向言語における設計方法 についてまとめたものになります。

そんな オブジェクト指向設計の原則 は下記の2つで、デザインパターンはこの考え方を元に作られたとされています。

1. インタフェースに対してプログラミングする

これは インタフェースを定義し、それを元に実装することで柔軟なコード設計にすることができる ということを示しています。

直接クラスを参照していたら依存関係が強まるばかり

2. クラス継承よりコンポジションを多用する

よく言われている話で、継承(is-a)よりもコンポジション(has-a)を使用する方が依存関係が少なく柔軟になるとされています。

ケースバイケースではありますが、同じ性質を持たせたい場合には継承、それ以外はコンポジション 等というように基本的にはコンポジションを使用するようにした方が依存は少なくなります。



学ぶメリット・デメリット

そんなデザインパターンですが、学ぶメリットとしては下記があると思います。

[メリット]

  • 最適なコード設計にたどり着きやすい。
  • 変更に強い柔軟なコードになる。
  • 共通認識として使用でき、把握しやすい。

「このパターンが使われてるな」といった共通認識があるのはいい

そして逆にデメリット、注意点としては下記があります。

[デメリット]

  • 使い過ぎてしまうこと。

学んだからといって使いすぎてしまうと、 コードが複雑になったり、パターンによっては実行速度が落ちる可能性もあります。 そのため、使用するかどうかの判断は慎重に行い、適切な場面で使用する ことが大事になってきます。

知識や技術を乱用してもいい結果にはならない。

どんなものがあるのかを知っておいて、必要になった時に引き出しとして持っておくというのがいいと思います



GoF23パターン

それでは最初に紹介したGoFのパターンを見ていきましょう!

GoFは全部で23パターンあって、

・生成に関するパターン ・構造に関するパターン ・振る舞いに関するパターン

の3つの分類に分かれています!

各パターンについて一言ずつ簡潔にまとめてみたので、 もし詳細が知りたくなったらパターン名でググって

生成に関するパターン

パターン名内容使いどころ
Abstract Factoryオブジェクトの生成処理を共通化するインタフェースを提供する。オブジェクトの生成を条件によって分岐したい時。
Builderオブジェクトの生成過程を共通化するインタフェースを提供する。オブジェクトの初期化を条件によって分岐したい時。
Factory Methodオブジェクトの生成と生成過程を継承元のサブクラス内メソッドで行う。

(Template Methodパターンを使用。Abstract Factory、Builderパターンよりは抽象度は下がる。)
オブジェクトの生成、初期化を条件によって分岐したい時。生成をクラスで分けるほど複雑でない時。
Prototypeオブジェクトの原型を作成し、コピーすることで作成する。オブジェクトを状態含めてクローンしたい時。
Singletonオブジェクトが1つ以上存在しないようにする。オブジェクトが複数あって欲しくない時。




構造に関するパターン

パターン名内容使いどころ
Adapterあるインタフェースをクライアントが求めるインタフェースに変換(拡張)するパターン。編集できないインタフェースを求める形に変換したい時。
Bridge実装を拡張するためのクラス階層(Impl)を持たせることでそれらを独立に変更できるようにする。実装クラスと拡張クラスを自由に組み合わせたい時。継承によってごちゃらせたくない時。
Composite容器と中身を同一化することで再帰的な構造を作る。ディレクトリ内にディレクトリとファイルが存在する、階層の中に更に階層があるような時。
Decorator責任(機能)をDecoratorとして作成し、オブジェクトに動的に追加する。柔軟に機能を追加、拡張したい時。
Facade複数のインタフェースに1つの統一インタフェースを与える。複雑さを軽減し、依存関係を小さくすることができる。複数のクラスの呼び出しをまとめたい時。複雑さを軽減したい時。
Flyweight同じインスタンスを別々の箇所で使用する場合に、インスタンスを再利用することでコストを抑える。文字オブジェクト、タイル等、複数種類のオブジェクトが大量にある時。
Proxyオブジェクトへのアクセスの代理、入れ物を提供する。間に挟むことで、オブジェクトへのアクセスが間接的になる。オブジェクトの中間的な処理を置きたい時。




振る舞いに関するパターン

パターン名内容使いどころ
Chain of Responsibility要求を受信するオブジェクトを鎖状に繋ぎ、処理が可能なオブジェクトに渡るまで次のオブジェクトに渡していく。受信側のオブジェクトを柔軟に追加、変更したい時。結びつきを緩くしたい時。
Command要求をオブジェクトとしてカプセル化することでコマンドの実行、取り消しを可能にする。コマンド実行をオブジェクトとして切り離したい時。実行したコマンドを管理し、取り消し/再実行を行いたい時。
Interpreter各クラスで構文解析の規則を表現し、解析結果を受け渡す。正規表現やSQL等で使用されている。構文解析を行いたい時。
Iterator集約オブジェクトの内部表現を公開せずに、要素に順にアクセスする方法を提供する。List等の内部表現を公開せずに順にアクセスしたい時。複数の走査オブジェクトに共通のインタフェースを提供したい時。
Mediatorオブジェクト間の相互通信を仲介する。Facadeが一方通行であるのに対し、Mediatorは双方向に通信を行う。オブジェクト間の通信を仲介させたい時。
Mementoオブジェクトの状態を外部に記録し、保存/復元を行う。状態の保存、取り消し/再実行を行いたい時。
Observerオブジェクトの状態が変更された時、自動的に知らされるよう一対多の依存関係を定義する。状態が変更されたことを複数のオブジェクトに通知したい時。
State各状態の振る舞いを表すオブジェクトを導入し、呼び出し側では状態遷移を定義する。オブジェクトの複数の状態を明確に分けたい時。
Strategy複数のロジックのインタフェースを変数として用意することで戦略的にロジックを切り替えるパターン。(コンポジション)アルゴリズム的な処理を入れ替えたい時。(ソート、テキスト処理等)
Template Method大まかな処理を基底クラスに定義し、具体的な処理はサブクラスに任せる。(継承)一連の処理の流れが決まっているが、様々なパターンがある時。
Visitorデータ構造と処理を分離する。訪問者クラスを用意し、データ構造の中を渡り歩いて処理を行う。オブジェクトに対するオペレーションを分離して柔軟性を持たせたい時。

以上で23パターンになります! 聞いたことがある名前も多かったのではないでしょうか? ざっくりした説明を見た上で実際に各パターンのコードを見てみるのが一番分かりやすいと思うので、是非ここから調べてみてください!

これまで無意識に実装していたのもこのパターンだったのか、 みたいなのも結構あるかと思います





ゲームプログラミング13パターン

これに加えてゲームプログラミングでよく使用するパターンとしてまとめられている「Game Programming Patterns」も有名な書籍になります。



この内容はWebでも公開されています(英語だけど) https://gameprogrammingpatterns.com/

内容としては、Commandパターン、ObserverパターンといったGoFの中でよく使用するパターンに加えて、ゲームプログラミングでよく使用する13パターンについて記載されています。

分類としては、

  • シーケンスのパターン
  • ビヘイビアのパターン
  • 分離のパターン
  • 最適化のパターン

の4種類に分けられています。

シーケンスのパターン

パターン名内容使いどころ
Double Buffer現在の状態、次の状態を持たせておき、切替を一瞬で行ったように見せる。描画処理等、情報を瞬時に切り替えたい時。
Game Loop入力に関わらず常にループを実行する。FPSを調整して間隔を調整する。入力に関わらず一定時間でループを実行したい時。
Update Method個々のオブジェクトに更新メソッドを持たせ、メイン処理から呼び出す。同時に多数のオブジェクトのループ処理を行いたい時。

ビヘイビアのパターン

パターン名内容使いどころ
Byte Codeビヘイビアを命令として持たし、独自の仮想マシンコードで実行する。プログラムの信頼性が最重要となり、使用している言語やツールに問題がある場合。
Subclass Sandbox継承によって共通処理を基底クラスにまとめる。Template Methodパターンとは逆に、処理の流れは各サブクラスが定義する。大まかな振る舞いはサブクラスが決めたい時。外部サービスへのアクセスを基底クラスにまとめたい時。
Type Object基底クラスに系統を表すクラス(Breed)を持たせて、オブジェクトごとに異なるデータを設定する。また、parentを持たせて親子階層も定義する。1クラスで複数系統のクラスを生成したい時。

分離のパターン

パターン名内容使いどころ
Component物理シミュレーション、グラフィックス等、ドメインごとにコンポーネントとして分割し、処理を委譲する。利用しているドメインを分離しておきたい時。継承では再利用したい部品がうまく組み合わせられない時。
Event Queue受け取ったイベント(メッセージ)をキューとしてグローバルに管理し、実行する。Observerの非同期版。イベント(メッセージ)を非同期に実行管理したい時。
Service LocatorSingletonなServiceとして一箇所に登録し、呼び出せるようにする。呼び出し側は基底クラスを指定することで差し替えも可能になる。Singletonを柔軟に管理したい時。呼び出し元をまとめたい時。

最適化のパターン

パターン名内容使いどころ
Data Localityポインタが飛び回らないように、またキャッシュミスを減らすよう意識して実装する。例えば同じクラスのデータは配列にまとめて処理する。柔軟性より高速化を求める時。キャッシュを効率よく使いたい時。
Dirty Flag古くなっていることを示すダーティ(汚い)フラグを用意し、必要な時のみ処理を行うようにする。位置情報更新処理など、都度計算すると重くなる時。
Object Poolオブジェクトの破棄を行わず、プールクラス内で使用状態を管理して使い回す。オブジェクト生成が何度も必要な時。生成によるメモリ断片化を防ぎたい時。
Spatial Partition空間を分割し、特定セル内のオブジェクトに対してのみ処理を行うことで効率化する。オブジェクトが広い範囲に散見している場合に効率よく判定を行いたい時。

Unityプログラミングパターン12種を公式デモ
https://github.com/Unity-Technologies/game-programming-patterns-demo



Unityでパターンを適用したサンプルをあげてくれている方のGitHub
https://github.com/QianMo/Unity-Design-Pattern