Unity_Lesson

デザインパターン

簡単に言うとどっかのソフトウェア開発者が設計ノウハウをパターンとしてまとめたもの。
用語が初めて発表されたのは、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 Locator Singletonな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