デザインパターンメモ
※ 数年前に書いてローカルに残っていた個人用メモを掘り起こしただけのものになります。
デザインパターンとは何か
一言でいうと設計のノウハウ集。また狭義ではGoFの23のデザインパターンを指す。
デザインパターンを適用することで「変更容易性」、「理解容易性」を高められる可能性が上がる。クラス設計におけるクラスの分割、責務の割り当ての1つのノウハウであり、特に『継承』『コンポジション』『ポリモフィズム』といった、オブジェクト指向設計の基本といえるテクニックを使った設計の延長線上にあるものである。
継承
継承はクラス間に親子関係を結び、親クラスの属性や操作などの特徴を継承することができる仕組み。例えば「法人顧客」は「顧客」であるというような、is-aの関係が成り立つ際に使用する。
継承は便利で強力ではあるが、スーパークラスの変更がサブクラスにそのまま影響するなど、その密結合な関係からシステムの複雑性が増してしまうことが多い。昨今では委譲のほうが好まれる印象。
コンポジション
コンポジションは、他のオブジェクトを取り込み、複合的なオブジェクトを構築する。クラスとクラスの間でhas-aの関係が成り立つ際に使用する。これは、デザインパターンで多く使われており、この特徴を押さえることもデザインパターンを理解する上で非常に重要。
AがBを保有し、処理を委譲している場合、Bに変更があったとしてもAは影響を受けにくいといった変更容易性が高い構造が作れる。
ポリモフィズム
ポリモフィズムは、オブジェクトに対して同じメッセージ呼び出しをした場合に実行されるメソッドが動的に決定され、異なる振る舞いをさせることができる特徴のこと。
呼び出すメソッドの仕様部分と実装部分がコンパイル時に決まるのではなく、実行時に決まる特徴で、この結合を遅らせることは非常に融通性の高い構造を作ることができる。これによってクラス構造の変更にも対応できやすい構造となる。
要するに具象ではなく抽象クラス、インターフェースを使って変更優位性、柔軟性を高めていきましょうやって話だと理解している。
- 抽象クラスやインターフェースを使って、具象クラスから抽象メソッドを引き剥がす
- 引数でインスタンスを渡す時やフィールドでインスタンスを保持する際は、具象クラスではなく抽象クラスやインターフェースの型にしておき、変更優位性を高める
みたいなのが肝。
結局のところ、デザインパターンってただの「インターフェース使いこなし特集」みたいなものだなと思っている。
- 具象ではなく抽象に依存する
- 継承ではなく委譲を使う
みたいなのをひたすらいろんな事例と共に語っていると思って眺めるのが良さそう。
Iterator
目的
何らかのリストから順次取り出す仕組み、繰り返し処理の実現。
イテレータを使う側からは具象クラスを見えなくすることで、実装依存せず処理を繰り返せるような構造を提供するもの。
for文などでも繰り返し処理は実現できるが、それだと毎回具体の実装に依存してしまう。Iteratorを使うことで、実装と切り離して繰り返し処理が行えるようになることが大きなメリット。
ただし、現代的な言語では言語仕様でIteratorをサポートしているケースが多いので、そんなパターンとして気にするケースは少ない印象。当たり前のように使っているという感じ。
登場人物
- Iterator
- 要素を順番にスキャンしていくインターフェースを定める役
- ConcreteIterator
- Iteratorが定めたインターフェースに従って具体の実装をする役
- Aggregate
- Iteratorを作り出すインターフェースを定める役
- ConcreteAggregate
- Aggregateが定めたインターフェースに従って具体の実装をする役
Adapter
目的
既存資産を流用するためのギャップ解消。委譲いいね。
例えばUSB-A対応の充電ケーブルしか持っていなくて、それをUSB-Cポートにつなぎたいときにそれらをコネクトするための変換アダプタを使う。
そのような「すでに提供されているもの」と「必要なもの」の間のズレを埋めるための仕組み。
- クラスによるAdapter (継承)
- インスタンスによるAdapter (委譲)
がある。
実現場では毎回ゼロからプログラミングをするわけではなく、例えばOSSのライブラリ (3rd-party APIのAPIクライアントとか)などを駆使して機能を作っていくことが多い。そのような既存の資産を活かして最短で事業上の目的を達成する上で、Adapterパターンは有効である。
登場人物
- Target
- いま必要となっているメソッドを定めている役
- Client
- Target役のメソッドを使って仕事をする役
- Adaptee
- Adaptee(適合される側)役
- すでに用意されているメソッドを持っている役
- このAdaptee役のメソッドがTarget役のメソッドと一致していたらAdapterパターンはそもそも不要
- Adapter
- Adaptee役のメソッドを使ってなんとかTarget役を満たそうとする
- クラスによるAdapterパターンでは継承、インスタンスによるAdapterパターンではコンポジション、委譲を使う
- 基本、コンポジションを使う方がメンテナビリティは高いはず
Template Method
目的
普通のabstract classの使い方、要するに継承。
スーパークラスで処理の枠組み(テンプレート)のみを決めておいて、サブクラスでそのテンプレートに従って詳細な内容を定める。
メリットとしては
- サブクラス間で共通のロジックをスーパークラスに集約 (ロジックの共通化)できる
- サブクラス側の実装のインターフェース、振る舞いをスーパークラス側で強制できる
- サブクラスの実装の自由度を下げる(制約を設ける)ことで複雑さを軽減できる、考えることをシンプルにできる
といった所か。Javaのabstract classの存在意義を示すようなパターン。
パターンというより、普通のabstractクラス、オブジェクト指向の継承の使い方を解説しているだけという印象。
登場人物
- AbstractClass
- テンプレートメソッドを実装する
- またそのテンプレートメソッドで使う抽象メソッドを宣言する
- ConcreteClass
- AbstractClass側で宣言された抽象メソッドの詳細を実装する
- ここで実装されたメソッドがAbstractClass側のテンプレートメソッド経由で呼び出されるイメージ
- AbstractClass側で宣言された抽象メソッドの詳細を実装する
Factory Method
目的
Template Methodの考え方をインスタンス生成の場面に適用したもの。操作と生成の関心を分離し、インスタンスの作り方をスーパークラス側で定め、具体的な肉付けをサブクラス側で行う。
これによってインスタンス生成の枠組み (フレームワーク)と実際のインスタンス生成のクラスを分けて考えることができ、具象クラスを書かず(具象クラスに依存せず)にインスタンス生成を実現できる (抽象に依存する形でインスタンス生成を実現できる)。スーパークラスが具体的なインスタンス生成の関心から解放される。
ぱっと見、やっていることの割にコードがむしろ複雑になったように見えがちなので注意。「具体ではなく抽象に依存する方向で考えられているか?」を気にしておけば実務上十分な気がした。
またインスタンス生成のクラスメソッド (staticメソッド)全般をFactory Methodと呼ぶこともあり、むしろこっちの方が一般的という印象。
登場人物
- Product
- フレームワーク側
- このパターンで生成されるインスタンスが持つべきインターフェースを定める抽象クラス
- Creator
- フレームワーク側
- Productを生成する抽象クラス
- Creatorは実際に生成するConcreteProductについて何も知らない
- 知っているのはProductの存在と、インスタンス生成メソッド(factory method)を呼び出せばProductが生成できる (以下でいう
create
)ということのみ new
による実際のインスタンス生成を、インスタンス生成のためのfactory method呼び出しに代えることで、具体的なクラス名による束縛からスーパークラスを解放している
- ConcreteProduct
- Productの具体的な肉付けをする側
- ConcreteCreator
- Creatorの具体的な肉付けをする側
Singleton
目的
「テストが書きにくくなり、メンテナビリティも下がるので使うのはやめておけ」と後世に伝えたくなるアンチパターン。
定義した当時の用途としては「このクラスのインスタンスは1つしか作らない」といったように、システム上たった1つしか存在しないようなものを作りたい際に使うことを想定していた:
- 指定したクラスのインスタンスが絶対に1つしか存在しないことを保証したい
- インスタンスが1つしか存在しないことをプログラム上で表現したい
ただし、現代においてSingletonは以下のようにデメリットのほうが目立つ。まあ使わない方が良いだろう。
- グローバルスコープで利用されがち、グローバル変数のように扱われがち
- Singletonはオブジェクト生成に制約を設ける分、生成するクラスへの結合度が高くなりがち
- ポリモーフィズムやダミーオブジェクトへの差し替えが難しくなる
- これはすなわち、テストが書きにくい/テスタブルではないとも言える
- 複数のテストが影響し合ってしまう、別の場所の影響でテストが失敗する、みたいなことが起きる
- 前のテストのコンテキストを引きずったまま、次のテストが実行されてしまう
参考: https://matsu.teraren.com/blog/2012/11/20/evil-singleton/
登場人物
- Singleton
- 唯一のインスタンスを得るためのstaticメソッドを持っている
- このメソッドはいつも同一のインスタンスを返す
- これにより、オブジェクト生成に制約を設けられる
Prototype
目的
生成するオブジェクトの原型をコピーして、新しいオブジェクトを生成する。
以下のような場合に有用:
- 種類が多すぎてクラスにまとめられない場合
- クラスからのインスタンス生成が難しい場合
- フレームワークと生成するインスタンスを分けたい場合
これはFactoryMethodと同じく操作と生成の関心を分離する目的で使われる印象だが、区別するとしたら
- Prototype: 状態を持つオブジェクトから、それを引き継ぐ形で新しいオブジェクトを生成したい場合に使う
- Factory Method: 上記以外の状態を持たないオブジェクトの生成に対して使う
という感じか。JavaScriptはプロトタイプ継承だが、そのプロトタイプもここから来てるかな。
参考: https://futurismo.biz/archives/2800/
登場人物
- Prototype
- インスタンスをコピーして新しいインスタンスを作るためのメソッド (
clone
など)を定める
- インスタンスをコピーして新しいインスタンスを作るためのメソッド (
- ConcretePrototype
- Prototypeが定めるコピー用のメソッドの具体的な実装を担当する
- Client
- インスタンスをコピーするメソッドを利用して、新しいインスタンスを作成する
Builder
目的
複雑なオブジェクトの構築処理を一箇所に集約できる。操作と生成の関心の分離。
委譲を意識してコードを書いていると自然とこのパターンを使っていることが多い。DirectorがBuilderに処理を委譲する形になる。
複雑な手順でオブジェクトを生成、構築する場合に使えるパターン。複数のオブジェクトのメソッドを決められた順番で呼び出さないといけないなど、毎回それを気にしながら実装するコストが高い場合に有用。
Factory Methodと似ているが、BuilderパターンはFactory Methodパターンに比べてミクロな視点で共通化しており、インスタンスそのものの作成過程をまとめるためのもの。
参考: https://www.macky-studio.com/entry/2019/06/05/030404
登場人物
- Builder
- 目的の複雑な構築処理をするためのインターフェースを定める
- こいつに構築処理に必要な部品となるメソッドが用意される
- ConcreteBuilder
- Builderのインターフェースを具体的に実装する
- 実際のインスタンス生成で呼び出されるメソッドがこいつに定義される
- Director
- Builderの提供するインターフェースを使って、目的の構築処理を担当する
- ConcreteBuilderには一切依存せず、Buiderにのみ依存する
- 具体的なBuilderを知らないからこそ、容易に入れ替えが可能
- いわゆるDependency Injection (DI)
- 受け口としてインターフェースを受け取れるようにしておいて、それを満たす具体のオブジェクトを渡す (依存性を注入する)
- テスト容易性、変更容易性につながる
- Client
- Builderパターン利用者
Abstract Factory
目的
抽象的な工場で、抽象的な部品を組み合わせて抽象的な製品を作る。つまり部品の具体的な実装には注目せず、インターフェースに注目して、部品を組み立て製品にまとめる。
Factory Methodの応用系という感じで、Creator -(creates)-> Product
のパターンを複数掛け合わせて製品を作るようなもの。Creator -(creates)-> Product
の部分自体を抽象化して、交換容易性を高めたようなもの (疎結合にしたもの)という感じか。
もし具体的な工場を新たに追加したい、となったとしてもAbstractFactory, AbstractProductが定義するインターフェースに従い、抽象的な部分を実装するだけで簡単に追加できる。
ただし、あまりこれ相当の実装を現場で見たことがないかも。
Factory Methodとの違い
-
両パターンのインターフェースの共通点
- 抽象メソッドであるファクトリメソッドの宣言を2つ以上持つ事
-
両パターンのインターフェースの違い
- AbstractFactoryの仕様は、上述の共通点のみ
- 基本クラスはインターフェース
- FactoryMethodの仕様は、AbstractFactoryのそれに加え、ファクトリメソッドを使って作られたIProductを使い、何らかの処理を行う実装メソッドを持つ
- 基本クラスは抽象クラス。
- AbstractFactoryの仕様は、上述の共通点のみ
参考: http://cyberboy6.blog.fc2.com/blog-entry-432.html
登場人物
- AbstractProduct
- AbstractFactoryによって作り出される抽象的な部品や製品のインターフェースを定める
- AbstractFactory
- AbstractProductのインスタンスを作り出すためのインターフェースを定める
- Client
- AbstractFactoryとAbstractProductのインターフェースのみを使って仕事を行う役
- 具体的な製品や工場については一切しらない
- ConcretetProduct
- AbstractProductのインターフェースを具体的に実装する
- ConcreteFactory
- AbstractFactoryのインターフェースを具体的に実装する
Bridge
目的
機能のクラス階層と実装のクラス階層の分離、そしてそれらを委譲によって橋渡しする(連携させる)。
スーパークラスを拡張してサブクラスを作ろうとなった際に「今、自分がやろうとしているのは機能の追加なのか、実装を行おうとしているのか」を見失わないよう、それらの階層を分離して考えましょうという方針。それらを一緒くたにして階層を切っていくと機能追加、実装追加どちらか分からない階層構造がどんどん生まれて辛いよね、なるべく疎結合にメンテできるようにしようねという話かな。
- 機能のクラス階層
- スーパークラスは基本的な機能を持っている
- サブクラスではスーパークラスに新たな機能を追加している
- 実装のクラス階層
- スーパークラスは抽象メソッドによって、インターフェースを規定している
- サブクラスは具象メソッドによってそのインターフェースを満たす実装をしている
みたいな感じ。
割と継承を前提としていたり、そんなクラス階層が複雑になるケースには個人的にあまり遭遇したことがないので、使うイメージがない。
登場人物
- Abstraction
- 機能のクラス階層の最上位クラス
- Implementor役のメソッドを使って基本的な機能だけが記述されている
- ここでImplementorに実装を委譲しているというのがポイントかしら
- RefinedAbstraction
- Abstraction役に対して機能を追加した役
- Implementor
- 実装のクラス階層の最上位クラス
- Abstraction役のインターフェースを実装するためのメソッドを規定する
- ConcreteImplementor
- Implementor役のインターフェースを実装する
Strategy
目的
アルゴリズムをカジュアルに変更可能にする。
メソッド内に溶け込む形でアルゴリズムをを実装してしまうと、アルゴリズムを変えたいとなった際にif文やswitch文でロジックを分岐させまくるようになって複雑になり、メンテナンスも大変になる。
このような課題に対し、あらかじめアルゴリズム部分を分離して委譲する形にしておき、戦略のインターフェースさえ揃っていれば容易に差し替え可能にできるよねというもの。
普通のポリモフィズムだし、比較的よく見かける印象。
登場人物
- Strategy
- 戦略用のインターフェースを規定する
- ConcreteStrategy
- Strategyのインターフェースを満たす実装を担当する
- 具体的な戦略
- Context
- Strategyを利用する役
- ConcreteStrategyのインスタンスを有しており、それを利用して(そいつに処理を委譲して)ロジックを実装する
Composite
目的
インターフェースの統一による再帰構造の実現。容器と中身を同一視して、再帰的な構造を作る。例えばディレクトリの中身がサブディレクトリか、ファイルか、といったことを気にせず、同一のディレクトリエントリとして再帰的にディレクトリ内をトラバースしていけるようなイメージ。
インターフェースを利用した典型的なパターン、比較的よく見るパターンな気がする。
登場人物
- Leaf
- 中身を表す役
- 容器ではないので、こいつの中に他のものを入れることはできない
- Composite
- 容器を表す役
- LeafやCompositeを中に入れることができる
- Component
- LeafとCompositeを同一視できるようにするための役
- Leaf, Compositeの共通のスーパークラスとして (共通のインターフェースを持つように)実装する
- Client
- Compositeパターンの利用者
Decorator
目的
委譲による機能拡張、デコレーション。Railsだと draper
とかを使って、ModelにView用のロジック追加したものをDecoratorとして利用したりする。
包まれる側、包む側 (自分と委譲先)のインターフェースを揃えることで、透過的に扱えるのが特徴。包まれる側を変更することなく、機能追加が行える。
登場人物
- Component
- 機能を追加する時の核になる役
- 核となるインターフェースを定める
- ConcreteComponent
- Componentのインターフェースを実装する
- Decorator
- Componentと同じインターフェースを持つ
- またDecorator役が飾る対象となるComponent役を有している (委譲する)
- ConcreteDecorator
- 具体的なDecorator役
Visitor
目的
データ構造と処理の責務分離。Compositeパターンの派生形みたいな位置づけ。
ディレクトリ構造やDOMツリーに対するトラバースのような、データ構造の中に様々な要素があり 、その要素に対して様々な処理を施したいときに使う。
インターフェースの統一による再帰構造の実現という点ではCompositeと同じ。要素に対して様々な処理を施したいかどうかというのが差分という感じか。
登場人物
- Visitor
- データ構造の具体的な要素ごとに「xxxを訪問した」という
visit(xxx)
メソッドを宣言する -
visit(xxx)
はxxxを処理するためのメソッドで、実際の内容はConcreteVisitorに記述される
- データ構造の具体的な要素ごとに「xxxを訪問した」という
- ConcreteVisitor
- Visitorのインターフェースを実装する
-
visit(xxx)
を実装し、個々のConcreteElementに対する処理を記述する - 例えば、ディレクトリトラバースにおける
currentDir
みたいなものをイメージするとわかりやすいが、visit(xxx)
を処理していく中で、ConcreteVisitorの内部状態が変化することもある
- Element
- Visitorの訪問先を表す役
- 訪問者を受け入れる
accept
メソッドを宣言する -
accept
メソッドの引数にはVisitor役が渡される
- ConcreteElement
- Elementのインターフェースを実装する
- ObjectStructure
- Element役の集合を扱う役
- ConcreteVisitorが個々のElementを扱えるようなメソッドを備えている
Chain of Responsibility
目的
責務のたらい回し。要求者が責任を委譲できそうな候補に対し、「お前これできるか?」と詰める -> できる場合はそいつが対応、できない場合は次の人に責任転嫁していく、というもの。
「この要求はこの人が処理すべし」という情報を中央集権的に持たなくてよい (最初の依頼さえ委譲して投げれば後はよしなに対応できる人が対応してくれる)というのがメリット。処理が遅くなるというデメリットもあるが、まあそこまでデメリットを気にするケースも多くないだろう。
委譲の1パターンとして覚えておくと良さそう。
登場人物
- Handler
- 要求を処理するインターフェースを定める役
- 次の人を保持しておき、自分で処理できない要求が来たら、その人にたらい回しする
- 次の人もHandlerインターフェース
- ConcreteHandler
- 要求を具体的に処理する、Handlerインターフェースを満たす実装をする役
- Client
- 最初のConcreteHandlerに要求を出す役
Facade
目的
複雑な処理をシンプルなインターフェースで提供。
窓口みたいな表現をよくされるが、要するに「複数の処理を束ねたメソッド」を用意して複雑性を解消しようということでは。インターフェースをなるべくシンプルに、少なくするというのが特徴。
例えば、HTMLを構築する際にFacadeとしてHTMLBuilderを用意 -> そいつがBodyBuilder, HeaderBuilderなどに適切に処理を委譲しつつ、全体の構築に責任を持つ、みたいな感じか。
複雑なロジックを内部に持ちつつ、外部に公開するインターフェースは call
メソッドだけ、みたいにするケースはRailsまわりの実装でよく見る (call
にするという慣習はRackの影響かな)。
参考: https://blog.ecbeing.tech/entry/2020/10/29/155503
登場人物
- Facade
- システムを構成しているその他大勢の窓口、統括を担当
- 高レベルでシンプルなインターフェースを外部に公開する
- APoSDで言及されている内容に近いかしら
- システムを構成しているその他大勢
- それぞれの仕事を行う
- Facadeのことを一切意識していないというのが重要
- Facadeから呼び出されて仕事はするが、その他大勢からFacadeを呼ぶことはない
- Client
- Facadeパターン利用役
Mediator
目的
登場人物が多い、かつそれらが互いに参照し合っているような場合に、結合度が高まらないように仲介者を置く。複雑性が増している時は、通信経路を一本化してシンプルにしましょう、という話。
参考: https://blog.ecbeing.tech/entry/2020/10/29/155503
まあぱっとメリットが浮かびにくいので、こちらの説明ぐらいがわかりやすかった。
登場人物
- Mediator
- Colleague役と通信を行い、調整を行うためのインターフェースを定める
- ConcreteMediator
- Mediatorのインターフェースを実装する
- 実際に調整を行う役
- Colleague
- Mediator役と通信を行うインターフェースを定める
- 同僚というよりは同列の機能みたいな印象
- ConcreteColleague
- Colleagueのインターフェースを実装する
Observer
目的
状態変化の観察と通知。
観察者と観測対象を疎結合に扱うことができるので、今でもよく使われる印象。Rxなどはこのパターンが基本になっている。
Observerは観察というより、Subjectから変更があったことを通知してもらうのを待っているという表現の方が近いので、Publish-Subscribe (パブサブ)パターンとも呼ばれる。
また、MVCにおける、ModelとViewもSubject-Observerの関係と言える。
登場人物
- Subject
- 観察される対象
- 観察者であるObserverを登録するメソッドと、削除するメソッドが宣言されている
- また現在の状態を取得するためのメソッドも宣言されている
- 具体的なObserver (ConcreteObserver)については知らない
- ConcreteSubject
- Subjectを具体的に実装する
- 状態が変化したら、それを登録されているObserverに通知する
- Observer
- Subjectから「状態が変化されたよ」というのを教えてもらう役
- そのためのメソッドが
update
- ConcreteObserver
- Observerの具体的な実装を持つ
-
update
が呼び出されると、そのメソッドの中でSubjectの現在の状態を取得する
Memento
目的
カプセル化の破壊をせずに状態の復元をできるようにする。
オブジェクトの任意の時点の状態を覚えておき (保存), 後でその状態にオブジェクトを戻すための工夫を提供するパターン。
- undo
- redo
- history
- snapshot
などを行えるようにする。
これも委譲をベースとしている。
登場人物
- Originator
- 自分の現在の状態を保存したい時にMemento役を作る
- 以前のMemento役を渡されると、そのMementoを作った時点の状態に戻る処理を行う
- Memento
- MementoはOriginatorの内部情報をまとめる
- Originatorの内部情報を有しているが、それを誰にでも公開するわけではない。2種類のインターフェースを持っている
- wide interface
- オブジェクトの状態を元に戻すために必要な情報がすべて得られるメソッドの集合
- Memento役の内部状態をさらけ出してしまうものなので、アクセスできるのはOriginatorのみ
- narrow interface
- 外部のCaretaker役に見せるためのもの
- できることに限りがある
- wide interface
- 2つのインターフェースを使い分けることでカプセル化の破壊を防ぐ
- Javaだとパッケージによるアクセス制御で実現できる
- Caretaker
- いつSnapshotを撮るか、いつundoするか、などを決め、Mementoを保持する役
- 現在のOriginatroの状態を保存したい時、それをOriginatorに伝える
- Originatorはそれを受けてMementoを作り、Caretakerに渡す
- Caretakerは将来に備えて、そのMementoを保存しておく
CaretakerとOriginatorで役割分担をすることで、
- 複数ステップのundoを行うようにしたい
- undoだけでなく、現在の状態をファイルに保存したい
などの変更にもOriginatorを変更せずに対応可能となる。
State
目的
状態をクラスとして表現する。状態がコロコロ変わるような複雑なケースで、状態一つ一つをクラスとして表現し、クラスを切り替えることによって状態の変化が表現できるようになる。
if文、switch文みたいなもので複雑な条件分岐をすることなく、ポリモーフィズムで状態管理ができる。これによって1つのConcreteStateの実装をしている際、他のクラスのことをある程度頭から追い出して考えることができるなど、対峙する問題をシンプルにすることができる。分割統治。
登場人物
- State
- 状態を表すためのもの
- 状態ごとに異なる振る舞いをするインターフェースを定める
- このインターフェースは状態に依存した振る舞いをするメソッドの集まりになる
- ConcreteState
- 具体的な個々の状態を表現する
- Stateのインターフェースを具体的に実装する
- Context
- 現在の状態を表すConcreteStateを持つ
- Stateパターンの利用者に必要なインターフェースを定める
Flyweight
目的
省メモリのためのパターン。
インスタンスをできる限り共有して、無駄にnewしないようにする。要するにキャッシュ。キャッシュ自体はよく使うパターンだが、ここまでシビアなメモリ対策はあまりしない印象。
またキャッシュするということは参照を持ち続けるということで、GCされないということなので注意。
登場人物
- Flyweight
- 普通に扱うとプログラムが重くなるので、共有したほうがよいものを表す
- キャッシュ対象
- FlyweightFactory
- Flyweight役を作る工場の役
- この工場を使ってFlyweightを作ると、インスタンスが共有される
- Client
- FlyweightFactoryを使って、Flyweightを作り出し、それを利用する
Proxy
目的
委譲による処理負荷の軽減。
本人の代理人となるオブジェクトが透過的なインターフェースを構えつつ、「どうしても本人でないと厳しい」という場合のみ、本人に処理を委譲するパターン。どうしても本人でないと無理になったら遅延評価的に委譲。
Decoratorパターンと近いが、Decoratorパターンは処理の拡張、Proxyパターンは負荷の軽減が目的という点で違う。分割統治と遅延評価の合わせ技という感じか。
まあ汎用的なインターフェースの使い方という感じ。最近ではオリジナルの処理に対してバリデーション挟むなど、多様な使われ方をProxyパターンと呼んでいる気もする。
参考: https://zenn.dev/morinokami/books/learning-patterns-1/viewer/proxy-pattern
登場人物
- Subject
- ProxyとRealSubjectを同一視するためのインターフェースを定める
- Subjectのおかげで、ClientはProxyとRealSubjectの違いを意識することなく処理をリクエストできる
- Proxy
- ProxyはClientからの要求をできるだけ処理する
- もし自分だけでは処理できないとなった場合はRealSubjectに仕事を委譲する
- Proxyは本当に必要になってからRealSubjectを生成するというのが肝 (遅延評価)
- Subjectを具体的に実装している
- RealSubject
- Proxyでは手に負えなくなったときに登場する
- Subjectを具体的に実装している
- Client
- Proxyパターンを利用する役
Command
目的
Stateの派生みたいなやつで、操作やコマンドをクラスとして表現する。CommandはEventとも呼ばれ、GUIに関するプログラミングでよく出てくる。
「ボタンがクリックされた」みたいなイベントが起こった際に、その出来事を一旦インスタンスというものにしておき、発生順に待ち行列に並ばせる。そして並んでいる順に処理をしていく。
登場人物
- Command
- 命令のインターフェースを定義する
- ConcreteCommand
- Commandのインターフェースを具体的に実装する
- Receiver
- Command役が命令を実行するときに対象となる役
- 命令の受け取り手
- Client
- ConcreteCommandを生成し、その際にReceiver役を割り当てる
- Invoker
- 命令の実行を開始する役
- Commandで定義されているインターフェースを呼び出す役
Interpreter
目的
構文木をクラスで表現する。
こんなもん普段のプロダクト開発の実装でやらんやろ。一般的なパターンとして定義するべきものではない。
登場人物
- AbstractExpressioin
- 構文木のノードに共通のインターフェースを定義する
- TerminalExpression
- BNF(バッカスナウア記法)のターミナルエクスプレッションに対応する役
- 参考: バッカス・ナウア記法
- NonTerminalExpression
- BNF(バッカスナウア記法)のノンターミナルエクスプレッションに対応する役
- Context
- インタプリタが構文解析を行うための情報提供をする
- Client
- 構文木を組み立てるためにTerminalExpression、NonTerminalExpression役を呼び出す
Discussion