記事を書く背景
コーディングの設計について考えることがあり、デザインパターンを改めて勉強したくなったためです。
書きたいコードのイメージに対して「デザインパターンのあれを使えば良いのでは?」と思い出せるように各デザインパターンの考え方や概要を自分用のメモとして残しておく目的です。
Java言語で学ぶデザインパターン入門第3版
を参考にしながら書きました。
ちょこちょこChatGPT相手に壁打ちしたので、それのリンクも載せています。
各デザインパターン
①Iterator
独自のデータ構造や特定の反復処理が必要な場合、カスタムのイテレータを検討したいときに参考にするかもしないパターン。
イテラブルなオブジェクトとかインターフェースで問題ないなら、ほとんど自分で実装することはないと思われる。
②Adapter
既存のクラスやデータを、新しいAPIやインターフェースで使えるようにしたい場合などに有用。
既に存在していたコードを変更することなくAPIを実行できるため、テストするのはAPIに渡すアダプター部分のコードだけで済む。
③Template Method
処理の順番は決めておきたいが、具体的な処理は場合によって分けたいときに検討するパターン。
例えば、
①外部データをインプット
②インプットしたデータを成形
③成形したデータを何らかの手段で保存
という処理をしたい場合に、①でどこからインプットする、②でどのように成形する、③どこにどう保存するというのをケースに分けて実装する。
しかし、上記のフロー自体は親のクラスに定義しておくことで、継承したクラスでは具体的な処理を実装すれば良いことになる。
④Factory Method
Tempate Methodの考え方をインスタンス生成に適用するパターン。
Productという抽象クラスと、おなじく抽象的なFactoryクラスを用意して、それぞれに対応させる形で具体的なクラスを実装する。
複数のProductを生成し、Product毎でのふるまいは似ているがちょっと違う、というときに使う。
Productにuse()という抽象メソッドを定義し、継承した具体クラスではuse()を実装すればProduct毎に異なる処理をさせることができる。
FactoryクラスからProductクラスを作成する処理を定義するため、FactoryとProductを継承した具体クラスは1 : 1の関係になる。
⑤Singleton
実行しているプログラム内で、インスタンスが絶対に1つしかないことを保証したいときに使うパターン。
DBへの接続情報や、ログの設定管理など全体で共通の情報はSingletonパターンの考え方で一つのインスタンスが作成できれば問題ないため、使われることがある。
⑥Prototype
インスタンスの生成コストを抑えるために、既存のインスタンスをコピーして新しいインスタンスを作成するときに有用なパターン。
例として、ゲームなどで同じ敵を大量生産するときに、Pythonだったらcopyモジュールを使って全く同じ中身の別のインスタンスを生成したりする。
⑦Builder
複雑な設定付きのオブジェクトを条件によって複数種類、かつ段階的に構築したいときなどに使うパターン。
Director、Builder、Builderを継承した具体Builderの3クラスで使う。
Directorは抽象クラスのBuilderしか知らず、Builderに対して指示を出すが実際の処理は具体Builderで行う。
やや使いどころがイメージしずらく、特にFactory Methodとの使い分けがむずかしい
chatGPTいわく、
🔧 「何を作るかが変わる」なら → Factory Method
🏗️ 「どう作るか(構成・順番)が複雑」なら → Builder
⑧Abstract Factory
以下のQiitaのまとめがわかりやすかった。
- 直訳では「抽象的な工場」ですが、いろんな部品を組み合わせて一つの製品を作るときに有効なパターンで、部品は固定であるときが有効
- 逆にいちいち部品が変わる場合は使わない方が良い
例えばvscodeをWindows向けとMac向けなどで作る場合、vscodeとしての部品は変わらないため有用かもしれない。
ただ、Webアプリを作る場合にはあまり使わなさそう。
⑨Bridge
とある機能に対して、表現方法を複数指定するような設計で有用。
参考書籍では以下のクラスが例として紹介されている。
- 機能のクラス:Displayクラス(抽象)とCountDisplay(具体)
- 実装のクラス:DisplayImplクラス(抽象)とStringDisplayImpl(具体)
StringDisplayImplはSystem.out.println()に対して結果の出力をさせているが、HtmlDisplayImplというクラスを追加し、Displayインスタンス生成時にHtmlDisplayImplインスタンスを渡せた、HTMLでの結果表示をさせることができる。
もちろん、結果表示のための実装はHtmlDisplayImpl内で行う必要がある。
⑩Strategy
Strategyパターンの肝はこの一文だと思う。
同じ問題を別の方法で解くのを容易にする
書籍ではじゃんけんで勝つための戦略をStrategyクラス(抽象)で定義し、具体的な戦略をStrategyクラスを継承したクラスで実装している。
個人的にはStrategyが委譲されているクラスがContextという命名がとてもわかりやすかった。
Contextクラスがつまり、「同じ問題」であり、それを解く方法が各Strategyということだと理解した。
別の書籍だが「良いコード/悪いコードで学ぶ設計入門 ―保守しやすい 成長し続けるコードの書き方」では「敵にダメージを与える方法」をStrategyクラスとして定義している。
⑪Composite
入れ物と中身を同一視して、オブジェクトを入れ子構造にしたい場合に有用なパターン。
わかりやすい例で言うとGUIでウィンドウを作り、その中にさらにパネルやボタンを追加するなど。
また、ファイルとフォルダのような関係の物だったら表現しやすい。
⑫Decorator
Compositeパターンと似て非なるもの。
単純な機能に対して、機能拡充させるような使い方をする。
そのために共通のインターフェースに対して実装して必要に応じて機能を追加して実装する。
⑬Visitor
VisitorはElementの内部構造には立ち入らず、Elementが提供するインターフェースを通じてデータを受け取り、独自の処理をするのが特徴のデザインパターン。
整理するためのにchatGPTとのやりとりはこちら。
https://chatgpt.com/share/680fe6d1-2a84-8003-b5c4-366b9d7aba78:title
⑭Chain of Responsibility
データが渡されてからどんな処理をさせたいかを動的に決定するためのパターン。
VisitorパターンではVisitor役が受け取ったデータに対して明示的に処理内容を切り替えていた。
しかしChain of Responsibilityパターンの場合はデータがどんなものかを各処理が判断し、適宜処理をするような考え方となっている。
そのためにデータを処理するインターフェースが鎖のように連なっているイメージ。
⑮Facade
複雑な一連の処理のAPIを作成して、処理を依頼する側はそのAPIを呼びさえすればよい状態にするパターン。
⑯Mediater
複数の要素の状態によって、それぞれに対して影響を与える場合にやり取りの仲介者を置くパターン。
参考書籍では画面内に以下のものがあるサンプルがあった。
- Guest or Loginを切り替えるラジオボタン
- UserNameを入力するテキストボックス
- Passwordを入力するテキストボックス
- ログインする場合のOKボタン
- ログインをキャンセルするためのキャンセルボタン
各要素の状態によって各々の活性/非活性を切り替えるのに、仲介者を立てて置き、活性/非活性を切りかえるロジックは仲介者のみが知っている状態にして、各要素はそれに従ういう考え方となっている。
⑰Observer
オブジェクトの状態を観察し、変更があった場合に検知できるような実装のパターン。
その変更に対してのイベントを実装したいときなどに有用。
⑱Memento
オブジェクトのスナップショットを取得しておき、そのスナップショットの状態にオブジェクトを復元させることができるパターン。
なんらかの処理の結果、オブジェクトの状態を戻したい場合に使うパターン。
ロジックの組み方次第かもしれないが、基本はオブジェクトが持っているインスタンス変数が復元される使い方となると思われる。
###⑲State
システムのふるまいがなにかしらの状態に依存する場合に有用なパターン。
同じ処理を実行する場合でも、実行時の状態によってふるまいを切り替える場合に、状態を管理するためのオブジェクトを生成して依存させるようにする。
参考書籍では昼間と夜間を状態として定義して、サンプルコードのふるまいを昼間状態と夜間状態で切り替える実装をしていた。
###⑳Flyweight
インスタンスをできるだけ共有して、無駄にnewしない
ときに使うパターン。
※参考書籍から引用
以下の条件を満たすときに有用かもしれない。
- インスタンスの生成コストが大きい
- 生成されたインスタンスは参照されるだけで、変更されない
- インスタンスそのもののメモリ使用量が大きい
ProtoTypeパターンでは、インスタンスの生成コストは大きいものの、複数のインスタンスを生成する必要がある場合に適用していた。
Flyweightパターンはインスタンス生成自体を最小限にしている。
###㉑Proxy
以下、参考書籍からの引用
proxyという単語は「代理人」という意味です。代理人というのは、仕事を行うべき本人の代わり(代理)となる人ですね。
本人でなくてもできるような仕事をまかせるために代理人をたてます。
同じインターフェースを実装し、代理人でも問題の無い処理は代理人に処理をさせて、本人が行うべき処理は本人にさせるという考え方のパターン。
代理人と本人とで分ける必要性については参考書籍に記載があり、個人的には分けた方がわかりやすいと感じた。
###㉒Command
命令をオブジェクトとして扱い、オブジェクト経由で処理を実行させたり、オブジェクトを実行履歴として保持しておくパターン。
参考書籍では具体的な命令オブジェクトであるDrawCommand(線を描画する命令)とMacroCommand(複数の命令オブジェクトに一括で命令したり、Undoを実装している)が紹介されていた。
GUIアプリなどでUndoがユースケースとして存在する場合に有用になりそうなパターン。
###㉓Interpreter
インタプリタ言語のようなふるまいをさせたいコードを書きたいときに参考にするパターン。
コードをインタプリタに見立てて独自に定義したインプットデータをコードに読み込ませて実行させる。
Pythonなどのインタプリタ言語ではソースコードをインタプリタが解釈して実行しているが、独自定義のデータを解釈するコードを自分で実装する。
感想
もっとサクッと書きたかったのに残業とかで書けない日もあってすごい時間かかった...
ただコードを読んだり考えるのは前よりも上手くなった気がするので結果書いて良かったです。