神クラスを作って今作りたい動きを作ることはできるとしよう。
しかし、実務で使うソースコードはただ動けばいいというわけではない。
コードの変更は容易にできるか?コードの意図を人に簡潔に説明できるか?問題の修正に精神的な負荷は少ないか?
これらの問題をクリアするための武器の一つとして、デザインパターンがある。
今までも何度か学ぶ機会はあったが、大きなシステムの開発・保守の経験が浅かった自分にとっては無用の長物であった。
「いつか役に立てばいい」ではない。
実際に現在使っている業務用のソースコードは中途半端な改変を繰り返し、見るに堪えない姿になっている。
このような現状を打破するべく、今までの自分への自戒の念も込めつつ、デザインパターンを学ぶ備忘録をここに記す。
- 「一つずつ順番にアクセスする」を抽象化した概念。
- データの持ち方や処理の仕方に依存せず、「順番にアクセスする」という部分のみを実装する。
- ConcreteIteratorがConcreteAggregateの実装に依存するのは仕方ない。
- 暗黙的に依存するくらいなら依存関係作っちゃった方が良い。
- Iterator(interface)
- 順次アクセスするためのインターフェース。
- next()とhasNext()がある。
- Aggregate(interface)
- 順次アクセス可能な集合体であることを表すインターフェース。
- イテレータを持っている。
- ConcreteElement(class)
- 集合の元のクラス。
- intとかでもOK。
- ConcreteAgregator(class)
- 集合の実態を持ったクラス。
- 集合はListや配列に限らず、「次」が定義されてればなんでもOK。
- ConcreteIterator(class)
- Iteratorを実装するクラス。
- 集合クラスを知っている必要がある。
- すでに提供されているものを使う際に良い感じにつなぎ合わせたり、別の機能として同じ処理をしたいときに良い感じに使ったりできるパターン。
- 直でもともとあるクラスの関数を使うのに比べて、名前を自由に付け替えれるのが強み。
- それによって同じ処理を「別機能として」使うことが可能。
- 名前を付け替えるパターンといってもよさそう。 再利用性がめちゃ上がる。
- クラスを継承するパターンだと結局ClientがAdapterを知ってないといけないから微妙な気もするが、踏み込むのがAdapterまででAdapteeと直接やり取りしていないのが大切な気もする。
- Target(class or interface)
- 今必要な処理があるクラスやインターフェース。
- これがクラスの場合とインターフェースの場合で敬称を使うか委譲を使うかが分かれる。
- Client(function)
- Targetを使って何かしたい人。
- Mainであることが多そう?
- Adaptee(class)
- Targetの処理として実装したい処理をすでに持っているクラス
- Adapter(class)
- Targetの必要な機能をAdapteeの機能を使って再現するクラス。
- 継承パターンか委譲パターンかで若干変わる
- 関数の具体的な実装をサブクラスに任せるパターン。
- virtualを使うというだけでなく、処理の流れをまとめた関数は作っておくというのが特徴。
- AbstractClass
- 処理の流れだけを定義している抽象クラス。
- 処理の流れのみをまとめた関数ははTemplate関数と呼ばれる。
- ConcreteClass
- 具体的な処理の中身を実装しているクラス。
- インスタンスの生成を担当するクラスを持っておくパターン。
- 利点は生成時の処理が重い(またはそれがメイン)の場合に、インスタンス生成を請け負うクラスを持って置けること。
- 例えば敵キャラの生成で、画面の敵をランダムに出現させたいとする。
- CreatorのCreateメソッドをTemplateメソッドと見立てればTemplatePatternをクラスの生成に適用したものと考えることもできる。
- Productのクラスが大局観を持たなくてもよい。
- Productを作成したら別の○○クラスに通知して登録して~みたいな、他クラスに依存する処理が多いときにそれを請け負うFactoryクラスを作ることで、Productがそれ以外と切り離せる。
- 例で、ConcreteFactoryクラスがProductのリストを持ってるのは感心しない。
- Factoryはインスタンスの生成のみを請け負うべきで、それらを管理するなら管理する用のクラスを作るべき。
- と思ってたけど、FactoryMethodはあくまでインスタンス生成を別のやつに任せるのが良いらしい。
- CreatorはProductをUseするだけにしたい!
- Creator(class)
- productを作るための抽象クラス。
- ここにproduct作成時のTemplateメソッドを書いておく
- ConcreteCreator(class)
- 実際にproductを作成する。
- どのproductでも共通して行いたい処理はCreatorのTemplateメソッドに書いてあるため、個別のproductごとのコンストラクタ的な奴を作ればOK。
- Product(class)
- 複数ある可能性のあるプロダクト。
- 例ではUse()が定義されているが、あってもなくてもOK。
- ConcreteProduct(class)
- 具体的なProductを実装するクラス。
- コンストラクタは内部的なことだけでOK。
- 例えば外部の配列に自分を登録したりとかはCreatorがやるからいい。
- インスタンスを1つしか持たないことを保証するパターン。
- Unityにおいてたまーに使いたくなる。
- BGMを鳴らすスクリプトをシーンごとに置きたくないとか。
- Singleton(class)
- 常に一つだけ存在したいようなクラス。
- すでにあるインスタンスから新しいインスタンスを作成するパターン。
- コピーコンストラクタがあればよさそうな気もするが、interfaceとしてPrototypeインターフェースを作っておくことが大事なんだと思う。
- 明示的にパターンを用いるという意味で。
- Prototype内にCloneのほかにも必要最小限の関数を入れて、「Product」、みたいなインターフェースにしてもいいかも?
- 全体に言えることだが、具体的なクラス名が出てこない抽象的なメソッドやクラスの作成は、再利用性という観点で作りまくるとよい。
- 現在作っている処理に必要な最小限のクラスは何かということを意識するとよい。
- 使用例
- 1.複数の(Productを継承した)クラスやコンストラクタから作ったインスタンスに名前を付けておいて、それをコピーしながら呼び出したいとき。
- モンスターのパラメータを設定してインスタンス化しておいて、あとから登録した名前でインスタンスをコピーする、とかで使える。
- 複数のクラスについて、そのインスタンス化を請け負ってくれるManagerとかを作っておくと楽ちん。
- Prototype(interface)
- ICloneableという名前でもいいかもしれない。
- デフォルトであるけど非推奨っぽい。
- ConcretePrototype(class)
- Prototypeの実装を行うクラス。
- Client(class)
- Prototypeを使うクラス。
- Prototypeのテスト以外はPrototypeを直で使うことは少ないと思う。
- ICopyableのみが必要な場面は少ないだろう。
- 一見するとTemplateパターンっぽい。
- 一つの機能を作る責任を分割しているのがキモだと思われる。
- 例えば「文章を作る」という機能を考えた時、内容を決めるのと表示の仕方を決めるのをそれぞれ別ロジックで行う。
- FactoryMethodPatternとの違いは、Product内部の生成処理が重いか、外部の生成処理が重いか。
- Prototypeパターンでもそうだったが、DirectorとかManagerとかはAdapterの役割を果たしているとも考えられる。
- 例の文書作成だとTemplateパターンも使えそうだったから使っといた。
- Builderクラス(インターフェース)に構築の際のTemplateを作らなくても、Directorが隠蔽すればOK。
- 「構築の全体像」という役割をBuilderが負うかDirectorが負うかという違い。
- こう書いてるとDirectorが負った方が良い気がしてきた。
- ClientがDirectorもConcreteBuilderも知ってないといけないのが若干気になる。
- FactoryMethodと上手く組み合わせれば何とかなりそう。
- ConcreteBuilderを作るFactoryを作っとくとか。
- DirectorがConcreteBuilderを知らなくていいというのは強いから、Clientが知るべきはDirectorだけとかにしたいやっぱ。
- ConcreteBuilderのコンストラクタ以外の変更に関してはClientは無関心なのはいいね!
- Builder(interface)
- Product作成に必要な機能のみを集めたinterface
- Director(class)
- Builderインターフェースを使ってプロダクトの「核」を作っていくクラス。
- 例えば文章の内容はここで決める。
- ConcreteBuilder(class)
- Builderインターフェースを実装するクラス。
- 「核」となる部分はDirectorが持っているので、「ガワ」を色んな種類で作るときのもの。
- 自分をDirectorのBuilderにセットして作ってもらう。
- Client(class)
- Builderを使用するクラス。
- 抽象的な工場、プロダクト、部品を全部抽象で作ってしまうパターン。
- これだけ抽象度を上げることで新しい具体的な工場を追加することは簡単。
- 逆に、全ての動きをある程度抽象的に定めてしまうため、部品の追加には弱い。
- FactoryMethodパターンとの違いは、FactoryMethodパターンはCreatorでProductをインスタンス化しないことが重要。AbstractFactoryパターンはProductを組み合わせて1つの複雑なProductを作るときに、それを丸ごとモジュール化する。
- Client視点になると、Concreteを気にしなくていいのが偉い。
- GAをClientとして「ゲーム」の骨組みをAbstractFactoryで作ればいろいろできそう。
- 基本的には以下のC#での命名規則を参照
- 省略は基本しない。スマートさより理解しやすさ。
- 名は体を表す。「GameMaster」のような神クラスになるべくして生まれるようなクラスは作らないように。
- クラス名:パスカル
- インターフェース名:パスカルケース & 先頭にI(大文字i)
- メンバ変数
- 基本的に名詞を使う
- private : キャメル
- それ以外 : パスカル
- フィールド名
- private : キャメル
- それ以外 : パスカル
- メソッド名:パスカル
- その他
- 変数の命名は「codic」も参考に
- 集約
- 「持っている」関係。
- インスタンスを持っていれば、集約先の変更に関して割と敏感に反応しないといけない。
- Create
- 「インスタンス化はするが、保持しない」ような関係。
- 例えばあるクラスをインスタンスするが、スーパークラスのインスタンスとして保持する場合はこれになる。
- ここでCreate先のコンストラクタ以外が変更されても気にしないでいられる。
- 弱Create
- 引数の構造を知らなくてもいいようなCreateの関係。
- Factory系はこれ。
- 継承
- クラスの継承を表す。
- SOLID原則のLのやつに気を付けて。
- 親クラスのインスタンスとして持っても問題なく機能する。
- 実装
- interfaceを実装するときに使うやつ。
- Use
- 集約との違いがイマイチ分からん。
- staticな関数を呼び出してるだけとかの時は使えそう。