Josh Kaufman氏によると、正しく学習を行えば20時間で新たなスキルを身につけることができるそうです。
今回は氏の学習方法を参考に、「デザインパターン」について20時間学習を行ってみたので、その結果をまとめてみたいと思います。
なお学習ノートとしての側面が強いので注意してください。
※他パターン・サンプルコード追加予定
はじめに
学習前の知識や技術
3年ほど前にデザインパターンの概要を学習したことはあるが、具体的な内容や実装方法についてまでは知らない。シングルトンなどかなり有名なパターンに関してはお試しとして実装したことがある。
一部のパターンは言語が提供する機能で代用することができたり、YAGINIやKISSに違反してしまったり、不適切な箇所に無理やり適用してしまったりなど、デザインパターンが銀の弾丸になることはない。
UMLのようにシステム設計時の共通言語として使うことができる。
学習目標
まだまだシステム開発の経験が浅く、使いこなせる自信がないため、あくまでお試しで実装ができるようになるレベルを目指したいと思います。
- デザインパターンの概要と実装方法を理解する
- デザインパターンが銀の弾丸ではないことを理解する
学習内容
実際に実装してみたデザインパターンは以下のとおりです。
Decorator, Proxy, Bridge, Composite
Template Method, Mediator, Observer, State, Command, Memento, Interpreter, Chain of Responsibility, Visitor
今回の記事では、その中でも特に気になったパターンをまとめます。
※ 他のパターンやソースコードなど少しずつ追加予定
GoFのデザインパターンとは
GoF(Gang of Four)のデザインパターンとは、ソフトウェア設計において一般的な問題に対する解決策に名前をつけ、再利用しやすいようにカタログ化したものです。これらのパターンはオブジェクト指向プログラミングやソフトウェア設計時に使われています。
GoFのデザインパターンは、エリック・ガンマ、ラルフ・ジョンソン、リチャード・ヘルム、ジョン・ブリシディースの4人によって提案され、彼らの著書「Design Patterns: Elements of Reusable Object-Oriented Software」にまとめられています。
デザインパターン批判
ハンマーしか持っていなければすべてが釘のように見える
デザインパターンは、一般的な問題に対する解決策を提供するためのガイドラインですが、必ずしも全ての状況に最適な解決策を提供するわけではありません。あくまで数ある解決策の一つであり、要件に適している場所に活用されるべきです。
ありもしない箇所に無理やりパターンを当てはめてしまったり、不要な場面でパターンを適用してしまうと、システムが複雑になり冗長なコードが増えてしまいます。これによりKISSやYAGNIといった原則に違反する可能性があります。
それぞれの問題や要件に応じて、デザインパターンが本当に必要なのか適切なのかを判断し、柔軟に適用することが求められます。
Mediatorパターン
Mediatorパターンは、オブジェクト間の相互作用をカプセル化し、オブジェクト間の直接的な結合を減らすためのデザインパターンです。このパターンは、各オブジェクトが勝手に他のオブジェクトと連携するのではなく、Mediator(調停者)と呼ばれるオブジェクトを通してやり取りを行います。
このパターンにより、オブジェクト間の結合度が低くなり、ソフトウェアの保守性や拡張性が向上します。
以下に、Mediatorパターンの主要な要素を紹介します。
- Mediator: Colleague役と通信を行うインターフェースを定めます
- ConcreteMediator: Mediator役のインターフェースを実装し、オブジェクト間の相互作用を制御する責任を持ちます
- Colleague: Mediator役と通信を行うインターフェースを定めます
- ConcreteColleague: Colleague役のインターフェースを実装し、Mediatorに通知を送ります
Facadeパターンも密結合の解消という似た目的がありますが、こちらのパターンは双方向ではなく、Facade側から一方的なやり取りを行う。という違いがあります。
Observerパターン
Observerパターンは、観測対象オブジェクト(Subject)の状態が変化した時、それを監視するオブジェクトに自動的に通知を行うデザインパターンです。
私はMediatorパターンとの違いに悩まされました。違いを調べていると、stackoverflowにて私と同様に悩んでいるという投稿を見つけました。最終的には、通知を行うのがObserverパターンの目的で、Mediatorパターンは相互作用を整理するという目的を達成するために通知を行う、と解釈しました。
以下に、Observerパターンの主要な要素を紹介します。
- Subject: 観測対象を表すオブジェクトです。Subject役は、監視者であるObserverを登録・解除するメソッドを持っています
- ConcreteSubject: ConcreteSubjectはSubjectの実装です。状態が変化すると、そのことを登録されているObserverに通知します
- Observer: Observerは、Subjectの状態が変化したと教えてもらう役です。そのためのメソッドを持っています
- ConcreteObserver: ConcreateObserverはObserverの実装です。通知を受け取ると、Subjectの現在の状態を取得し、それに応じた処理を行います
Mementoパターン
Mementoパターンは、カプセル化を壊すことなくオブジェクトを復元することができるデザインパターンです。オブジェクトの情報を保持し、保存しておいた情報から元の状態に復元することができます。
オブジェクトを復元するためには、内部の情報に自由にアクセスできる必要があるのですが、このパターンを使用することでカプセル化を壊すことなく保存と復元を行うことができるようになります。
以下に、Mementoパターンの主要な要素を紹介します。
- Originator: Originatorは内部情報を持つ元オブジェクトです。Originator役はMementoの作成や渡されたMementoを基に復元を行います
- Memento: MementoはOriginator役の内部情報を保持するオブジェクトです。Mementoは不変であり、Originator以外のオブジェクトからはアクセスすることができません
- Caretaker: CaretakerはMementoを管理するオブジェクトです。Caretaker役はMementoの保存をOriginatorに伝えたり、将来に備えて複数のMementoを保存します
Commandパターンと組み合わせることで、命令のアンドゥやリドゥを行うことができます。
Stateパターン
Stateパターンは、オブジェクトの内部状態に基づいて振る舞いを変更するためのデザインパターンです。このパターンでは、オブジェクトの状態をクラスとして表現し、状態ごとに異なる振る舞いを実装します。
状態をクラスとして表現することで、クラスを切り替えることによって状態の変化を表すことができ、新しい状態の追加も用意に行うことができます。
以下に、Stateパターンの主要な要素を紹介します。
- State: Stateは状態を表します。各具体的な状態はこのState役を実装し、状態に基づく振る舞いを提供します
- ConcreteState: Stateの実装です。Context役からの要求に応じて、他の状態に遷移したりすることがあります
- Context: Contextは、現在の状態を表すConcreateState役を持ちます
Stateパターンを実装するときに、状態遷移を誰が管理するべきか、ということに悩まされました。ConcreteState役が行うか、Context役が行うか。今回はConcreteState役が状態遷移を管理しましたが、Context役にすべて任せても良さだと思いました。
Commandパターン
Commandパターンは、操作をオブジェクトとしてカプセル化するデザインパターンです。Stateパターンでは状態をクラスとして表現しましたが、こちらのパターンでは、命令をクラスとして表現します。
このパターンを使用することで、命令を履歴として保存できるようになる他、命令群の再実行や取り消しを行うことができるようになります。
以下に、Commandパターンの主要な要素を紹介します。
- Command: 命令のインターフェースを定めます。命令の実行メソッドを持っています
- ConcreteCommand: Commandの実装です。実行メソッドの具体的な実装を行います
- Receiver: Command役が命令を実行するときに対象となる役です
- Invoker: Invokerは、Commandオブジェクトを保持し、命令の実行を開始する役です。Command役で定義されているメソッドを呼び出す役になります
Interpreterパターン
Interpreterパターンは、特定の言語や表現を解釈し、それに基づいた動作や処理を行うためのデザインパターンです。このパターンでは、文法を表現するためのクラス階層やデータ構造を構築し、それを解釈するためのインタプリタを実装します。
例として、クエリ言語の解釈や正規表現などが挙げられます。
以下に、Interpreterパターンの主要な要素を紹介します。
- AbstractExpression: AbstractExpressionは、共通のインターフェースを持ち、具体的な解釈を行うためのメソッドを提供します
- TerminalExpression(終端式): TerminalExpressionは、文法の最小単位であり、解釈される言語の終端記号に対応します
- NonTerminalExpression(非終端式): NonTerminalExpressionは、複数の終端式やほかの非終端式を組み合わせて、より複雑な文法要素を表現します
- Context: Contextは、インタプリタが構文解析を行うための情報を提供する役です
まとめ
今回はGoFのデザインパターンについて再度学習を行いました。色々と調べながら一通り実装してみましたが、パターンを最適な箇所に適用できるようになるにはまだまだ経験と知識が足りないなと感じました。
デザインパターンに拘りすぎず、柔軟な設計を行っていきたいと思います。