blog

システム・アーキテクチャ設計ノート - デザイン・パターン

オブジェクト指向技術は、ソフトウェア技術に新たな発展をもたらします。人々は、オブジェクト指向のアイデアを使用してシステムを分析し、システムをモデル化し、システムを設計し、最終的にオブジェクト指向プログ...

May 24, 2020 · 25 min. read
シェア

オブジェクト指向技術は、ソフトウェア技術に新たな発展をもたらしました。人々はオブジェクト指向の考え方を使ってシステムを分析し、モデル化し、設計し、最終的にオブジェクト指向プログラミング言語を使って実装します。しかし、オブジェクト指向の設計は非常に単純なものではなく、特に、よく設計されたソフトウェアシステムを設計することは容易ではありません。システムの再利用性を向上させるために、クラスのインタフェースを定義し、クラスの継承構造を計画し、クラス間の関係を確立するなど、いくつかの「余分な」設計を実施する必要があります。間違いなく、良い設計はシステムを再利用、移植、保守しやすくすることができ、良い設計を迅速に実行する方法は、問題を議論するためのデザインパターンです。デザインパターンはソフトウェアアーキテクトにとって必須のものであり、デザインパターンに含まれる考え方はアーキテクトが習得すべき必須のものです。

デザイン・パターンの概要

デザイン・パターンの概念

まず、ファクトリーパターンはクラス生成の問題を解決するものであり、アダプターパターンはクラス・インターフェースの不一致の問題を解決するものです。Aの問題を解決するデザインパターンをBの問題を解決するために使っても、結果は同じです。ですから、デザイン・パターンを説明する前に、まずそのデザイン・パターンがどのような問題を解決しようとしているのかを説明します。

第二に、デザインパターンは汎用的な解決策であり、特定のものでも独自のものでもありません。GoFの本におけるデザイン記述はアイデアに焦点を当てており、C++の実装が示されていますが、Javaや、オブジェクト指向でない言語でも実装することができます。例えば、ファクトリーパターンには多くのバリエーションがあります。

ソフトウェアのデザインパターンはGoFの仕事で最初に提案されたものですが、この4人が作ったものではなく、多くのプロジェクトの成功設計から生まれたものであり、これらのエレガントな設計アプローチを抽象化し、要約し、一般化したものであることを指摘することは重要です。

デザイン・パターンを学ぶ際に注意すべき点は2つあります。デザイン・パターンを学ぶことは1つの側面であり、もう一方では、デザイン・パターンの考え方を理解することがより重要です。デザイン・パターン自体は、ソフトウェア・アーキテクチャの質を向上させるために設計されており、デザイン・パターンを学ぶ目的は、アーキテクチャ設計のレベルを向上させることでもあります。ほとんどのデザインパターンは、低レベルのオブジェクト指向設計ソリューションを記述していますが、ソフトウェア設計の考え方を含んでおり、ソフトウェアアーキテクチャのスタイルと一致しています。例えば、MVCはデザインパターンであると同時にアーキテクチャのスタイルでもあります。このようなデザイン思考をマスターすることは非常に有意義です。デザインパターンはデザインをより繊細にすることができますが、デザインパターンの使い方を誤ると逆効果になることもあります。デザイン・パターンをソフトウェア設計に用いることで、設計を最適化し、アーキテクチャの品質を向上させることができます。第二に、デザインパターンは主に相互通信、相互依存のオブジェクト間の構造的な関係を解決するために使用され、アーキテクチャ設計者は、デザインパターンの使用の強さを把握する必要があり、デザインパターンの過度の使用は、ソフトウェアの再利用性を向上させないだけでなく、アーキテクチャが混沌とし、維持することが困難になります。

デザイン・パターンの構成要素

一般的に、デザイン・パターンを説明するときには、少なくとも4つの側面を含める必要があります:パターンの名前、問題、解決策、そして効果です。これらの4つの側面は、デザイン・パターンの4つの要素です。

デザインパターンの目的は、問題を解決することですので、もちろん、デザインパターンの説明では、メソッドの説明の問題を解決するために記述する必要があります。これは、デザインパターンのもう一つの要素である解決策です。建築家は、デザインパターンの適用がアーキテクチャの品質を向上させ、ソフトウェアの再利用性を向上させることができることを知っていますが、各デザインパターンについては、効果のより具体的な記述があるので、デザインパターンの最後の要素は効果です。

これら4つの要素は、デザイン・パターンを記述する上で欠かせないものです。

GoF デザイン・パターン

GoFの研究は、設計で使用される一般的なパターンを初めて要約し、また、学界におけるソフトウェア設計パターンの地位を確立しました。そのため、GoFが提案した23のパターンを総称してGoFパターンと呼ぶのが通例で、以下に簡単に説明します。FactoryMethodパターン。 FactoryMethod パターンは、クラス生成の遅延メソッドを提供します。このメソッドを使用して、サブクラスは実行時にクラスのどのインスタンスを生成するかを決定できます。AbstractFactory パターン。 AbstractFactory は Abstract Factory パターンとしても知られており、複雑なシステムにおけるオブジェクト生成の問題を解決するために設計されています。AbstractFactory パターンは、類似した基底クラスや類似したインターフェースを持つ一連のオブジェクトを作成するための、一貫したオブジェクト作成インターフェースを提供します。Abstract Factory パターンは非常に代表的な設計パターンです。Builder パターン。 Builder パターンは AbstractFactory パターンによく似ていますが、Builder パターンは複雑なオブジェクトを段階的に構築し、最後にそのオブジェクトのインスタンスを返します。 Builder パターンは、複雑なオブジェクトの作成とその表現を分離することができ、同じ作成プロセスで異なる表現を作成することができます。Prototypeパターン。 Prototypeパターンは、プロトタイプのインスタンスに基づいて作成するオブジェクトの種類を定式化し、プロトタイプを深くコピーすることによって新しいオブジェクトを作成することができます。 Prototypeパターンは、AbstractFactoryパターンやBuilderパターンと同じ効果を持ちますが、インスタンス化するクラスが実行時に指定され、製品と並列なファクトリークラス階層を作成することを避けたい場合に使用できます。Prototype パターンは、実行時にプロトタイプを追加または削除するために使用でき、AbstractFactory パターンや Builder パターンよりも柔軟性があります。Singletonパターン。 Singleton パターンも代表的なパターンです。Singleton パターンを使用すると、クラスのインスタンスが 1 つだけであることが保証されるため、単一のグローバル・アクセス・ポイントが提供されます。Adapter パターン。 Adapter パターンは、システム間で互換性のないインターフェースの問題を解決します。アダプターは、クラスのインターフェースをクライアント・アプリケーションが望むインターフェースに変換することで、再利用性を向上させます。Bridge パターン。 Bridge パターンは、クラスの抽象部分と実装部分を分離し、クラスの抽象と実装の両方を独立して変更できるようにします。Composite パターン。 Composite パターンは、オブジェクトをツリー構造でグループ化する方法を提供します。 Composite を使用すると、個々のオブジェクトとグループ化されたオブジェクトの間で一貫性が保たれるため、ソフトウェアの再利用性が向上します。Decorator パターン。 Decorator パターンを使用すると、オブジェクトのメソッドに動的に機能を追加することができます。多くの場合、Decorator パターンを使用することで、新しいサブクラスを継承することなく、きれいなクラス継承構造を維持することができます。Facade パターン。 Facade パターンは、クラスのグループに対して一貫したアクセス・インターフェースを提供します。ファサードは、内部で異なるインターフェイスを持つクラスをカプセル化し、外部からそれらにアクセスする統一された方法を提供するために使用することができます。 Facade パターンは、J2EE システム開発では Session Facade パターンに発展しました。Flyweightパターン。 Flyweightパターンを使用すると、多数の細かいオブジェクトを共有できるため、オブジェクトの作成に割り当てられるスペースを節約できますが、時間オーバーヘッドが大きくなります。Proxyモード。その名前が示すように、Proxyパターンはオブジェクトに一種のアクセスプロキシを提供し、それを通じてオブジェクトProxyはクライアントアプリケーションのアクセスを制御することができます。たとえば:アクセス権の制御、アクセスアドレスの制御、アクセス制御など、さらにプロキシを介してゼロに大きなオーバーヘッドのアクセスを持って、アクセス効率を向上させます。インタプリタモード。与えられた言語と文法に従った文章を解釈するインタプリタを定義します。TemplateMethodパターン。操作のテンプレートを定義し、いくつかのステップをサブクラスで実装することで、さまざまな状況に対応できるようにします。Chain of Responsibility」(責任の連鎖)パターン。 Chain of Responsibility パターンは、リクエストに応答できるオブジェクトをチェーンに編成し、リクエストをオブジェクトのチェーンを通して渡します。これにより、複数のオブジェクトがリクエストを処理する機会を確保し、リクエストする側と応答する側のカップリングを回避します。コマンドパターン。リクエストをオブジェクトにカプセル化し、パラメータ化、キューイング、ロギングなど、リクエストの能力を強化します。Iterator パターン。 Iterator パターンは、オブジェクトのコレクションの要素への逐次的なアクセスを提供します。 Iterator を使用することで、コレクション内のオブジェクトの結合を公開することを回避できます。Mediator パターン。 Mediator パターンは、システム内のオブジェクト間の結合を減らします。 Mediator パターンは、中間オブジェクトを使用して他のオブジェクトをカプセル化し、カプセル化されたオブジェクト間の関係が疎結合になるようにします。Mementoパターン。 Memento パターンは、カプセル化を解除せずにオブジェクトの状態をキャプチャする方法を提供します。また、オブジェクトの状態を外部に保存し、必要なときに復元することもできます。Observer パターン。 Observer パターンは、オブジェクトの状態をオブザーバーのグループにブロードキャストする方法を提供し、各オブザーバーがいつでもオブジェクトの更新を通知できるようにします。State パターン。 State パターンは、オブジェクトの内部状態が変化したときに、オブジェクトがその振る舞いを変更することを可能にします。Strategy(戦略)パターン。Strategyパターンを使用すると、オブジェクトのアルゴリズムをクライアントに依存せずに変更することができます。Visitor モード。オブジェクトの構造の要素に対する操作を表す Visitor パターンを使用すると、クラスを変更せずに要素に対する新しい操作を定義することができます。

その他のデザイン・パターン

GoFの後、人々はデザイン・パターンの探求を続け、より多くのデザイン・パターンを考え出しました。J2EE アプリケーションの分野でも、J2EE フレームワークを使用して開発されたアプリケーションのためのデザイン・パターンが数多く特定されています。たとえば、Intercepting Filter パターンです。J2EE BPS アプリケーションフレームワークでは、クライアント認証、クライアントセッションの正当性確認、文字セットのトランスコーディング、クライアントリクエストのロギングなど、実際にクライアントリクエストに応答する前に何らかの前処理を行う必要があることがよくあります。もちろん、これらのリクエストを各Servletで前処理することもできますが、前処理コードが実際のプロセッサに「侵入」することは明らかであり、コードの保守が難しくなります。 Intercepting Filter パターンはこの問題の解決策を提供します。これはクライアントのリクエストをインターセプトし、リクエストを Filter チェーンに送り、これらの処理が終わるまで段階的に前処理を行い、リクエストは実際にクライアントのリクエストに応答する Servlet に転送されます。

デザインパターンとソフトウェアアーキテクチャ

例えば、古典的な「4+1」ビューでは、論理ビュー、開発ビュー、プロセスビュー、物理ビュー、シナリオビューを通してソフトウェアアーキテクチャを記述します。これらのビューでは、クラス間の関係、プロセス間の関係、ソフトウェアシステムにおけるソフトウェアとハードウェアの組み合わせが記述されます。

一般的に、ソフトウェア・アーキテクチャは、全体的かつグローバルな視点からソフトウェアの構成を記述する傾向があります。一方、デザイン・パターンは、クラス間やオブジェクト間の関係に重点を置きます。例えば、論理的な見方では、複数のデザインパターンを使ってクラス間の関係を整理することができます。したがって、デザイン・パターンとソフトウェア・アーキテクチャは、異なるレベルの問題に対する解決策であると考える人はたくさんいます。デザイン・パターンと同様に、ソフトウェア・アーキテクチャにも、しばしばアーキテクチャ・スタイルと呼ばれるいくつかの固定パターンがあります。

一般的なアーキテクチャ・スタイルには、レイヤード・アーキテクチャ、クライアント・サーバ・アーキテクチャ、メッセージ・バス、サービス指向アーキテクチャなどがあります。ソフトウェア・アーキテクチャーのスタイルは、ある意味でデザイン・パターンと同じです。デザイン・パターンとソフトウェア・アーキテクチャに組み込まれている考え方の多くは同じです。

アーキテクチャー・スタイルであれ、デザイン・パターンであれ、優れたデザインを追求する過程で、人々はいくつかの共通の解決策をまとめ、照合して、固定したスタイルやパターンを形成します。例えば、メッセージ・バス・アーキテクチャはObserverパターンに似ています。したがって、デザイン・パターンをマスターすることは、ソフトウェア・アーキテクチャの設計にとても役立ちます。

デザイン・パターンの分類

デザイン・パターンは問題指向である、つまり、それぞれのデザイン・パターンは特定のタイプの問題を解決するために設計されている、と言うことができます。したがって、デザイン・パターンは、解決しようとする問題に基づいて、3つのカテゴリーに分類されます。

実際、オブジェクト指向設計で解決すべきことは、システム内のオブジェクトをどのように管理するか、システム内のクラスとオブジェクトをどのように整理するか、システム内のクラスとオブジェクトがどのように互いに通信するか、ということです。この3つの問題を解決するのが、この3種類のデザインパターンなのです。

作成ベースのデザイン・パターンは、主にオブジェクトの作成の問題に対処します。最も単純なケースでは、プログラムの中でクラスが定義され、それが使用されるときにオブジェクトのインスタンスが作成されます。しかし、実際の開発では、オブジェクトの作成はもっと複雑になります。その場合、オブジェクトの作成の問題を解決するために、作成ベースのデザインパターンを使用する必要があります。

開発システムの拡張が進み、システムの機能が豊富になり、モジュール間の再利用性が高まると、システム内のクラスやオブジェクトの構造はより複雑になります。もし良い設計がなければ、これらのクラス間の関係は非常にわかりにくくなります。構造デザインパターンは、このような問題を解決するために設計されています。GoFはこの分類に加えて、デザインパターンが主にクラスに適用されるのか、オブジェクトに適用されるのかによって分類することも提案しています。この2つの分類方法を組み合わせると、GoFパターンは以下の表のように分類することができます。

クラスへの適用 Factory Method Adapter Interpreter, Template Method
オブジェクトに適用 Abstract Factory, Builder, Prototype, Singleton Adapter, Bridge, Composite, Decorator, Façade, Flyweight, Proxy Chain of Responsibility, Command, Iterator, Mediator, Memento, Observer, State, Strategy, Visitor
Intercepting Filter, Front Controller, View Helper, Composite View, Service to Worker, Dispatcher View Business Delegate, Value Object, Session Façade, Composite Entity, Value Object Assembler, Value List Handler, Service Locator データアクセスオブジェクト、サービスアクティベータ

デザイン・パターンと実装

ここでは、よく使われるいくつかのデザインパターンについて、その実装方法を説明します。

Abstract Factory

モデル名

Abstract Factoryは、Abstract Factoryパターンとも呼ばれます。

解決しようとする問題

プログラム内でオブジェクトを作成することは、これ以上簡単なことはないように思えますが、そうではありません。大規模なシステムの開発では、次のような問題があります:オブジェクトを作成する最も一般的な方法は、オブジェクトの新しいClassNameですが、このメソッドは、クラス名のハードコーディングになり、同じインターフェイスを持つ異なるオペレーティング環境に応じて動的にロードする必要がありますが、クラスのインスタンスの異なる実装では、このようなメソッドの作成は、別のオブジェクトとしてインスタンス化され、判断の複雑さと結合する必要があります。異なる動作環境に対応するために、抽象クラスでインタフェースを定義し、この抽象クラスのサブクラスを異なる動作環境で実装することがよくあります。共通の作成方法は、必然的にコードを実行環境に強く縛ることになり、ソフトウェア製品を他の実行環境に移植することはできません。

Abstract Factoryパターンは、構成やコンテキスト環境に応じて、同じインターフェイスを持つ異なるクラスインスタンスをロードすることで、このような問題を解決します。

モード説明

Abstract Factoryパターンの構造を図1に示します。

抽象ファクトリーの名前が示すように、抽象ファクトリークラスはクライアントからの " 注文 " を受け付けます。既存の" 製品モデル" に基づいて、特定の" 製品" -- Abstract Product を生成します。異なるワークショップは、顧客が使用するために異なる製品を生産し、ワークショップと製品の間の関係は一対一の対応です。すべての製品は、同じインターフェイスを持つ製品モデル - 抽象製品 - に従うので、これらの製品は顧客に直接配信することができます。Abstract Factoryパターンでは、Abstract Factoryは、製品ライン1を作成するCreate Productと製品ライン2を作成するCreate Product 2()のように、複数の類似したものを持つことができます。

効果

Abstract Productパターンは、オブジェクトの設定可能で動的な作成を実現するために使用することができます。Abstract Productパターンを柔軟に使用することで、ソフトウェア製品の移植性を向上させることができます。特に、ソフトウェア製品が複数のプラットフォームで実行される場合、または異なる機能構成バージョンを持っている場合、抽象ファクトリパターンは移植とリリースの圧力を軽減し、ソフトウェアの再利用性を向上させることができます。

関連ディスカッション

実際には、Abstract Factoryの方が柔軟性があります。実際、Abstract Factoryパターンをよく見てみると、Clientにとって最も重要な関心事は、異なる条件下で同じインターフェイスを取得することですが、オブジェクトの実装は異なるため、クラス名のハードコーディングを避ける限り、他の方法を使用しても実現できます。ですから、他の方法で実装することもできます。例えばJavaでは、図2のようにインターフェースとして実装することができます。

図 2 に示すクラスは Abstract Factory の考え方を適用したもので、Factory パターンをインターフェイス指向の方法で単純に実装したものです。 Product Factory は、Abstract Factory または Concrete Factory と見なすことができます。

商品を取得するメソッドとして getProduct が用意されており、商品名に基づいて特定の商品を作成し、それを返します。すべての製品は Product インターフェースを実装しており、クライアント・アプリケーションで使用できます。

抽象ファクトリ・パターンと同様に、ファクトリ内のオブジェクトを取得するメソッドは、実際の製品ラインの数と同じです。新しい製品ラインを追加する場合、例えば、新しい製品インタフェースである Product X を定義する場合、対応する getProduct() メソッドを追加する必要があります。このアプローチでは、抽象ファクトリと具象ファクトリを 1 つの製品ファクトリに統合するため、製品イン ターフェースを実装する新しい製品を追加しても、コードの変更は必要ありません。

このインターフェイス指向のアプローチに加えて、ファクトリーパターンはより多くのバリエーションを持つことができます。私は前に言ったように、究極の学習デザインパターンは、設計思考を学ぶことであり、ファクトリパターンを学んだ後、あなたが知っておくべきこと:①設定可能なオブジェクトの作成方法は、システムの移植性と再利用性を向上させることができます。オブジェクト指向のポリモーフィズムの特性をフルに活用するハードコーディングのオブジェクトの作成プロセスを回避することができます。

Singleton

モデル名

シングルトンは、シングルピースモードやシングルルートモードとも呼ばれます。

解決しようとする問題

ソフトウェア開発において、開発者は、あるサービスクラスが、他のアプリケーションが使用するために、1つのインスタンスしか持たないことを望みます。例えば、ショートメッセージサービスやプリンタサービス、あるいはシステム構成環境の制御など、同時アクセスによる不整合を避けるために、他のプログラムに対して1つのインスタンスだけを提供することが望まれます。

グローバル変数は、システム全体で使用することを意図したオブジェクトに使用することができますが、正しいコーディングのために一意のインスタンスが使用されたことを保証するだけです。しかし、システムが拡張し続け、開発チームが大きくなっても、このクラスのインスタンスがシステム内に1つしかないという保証はありません。

モード説明

Singletonパターンは上記の問題を解決するもので、その構造を図3に示します。

Singletonは構造的には最も単純なパターンですが、非常に汎用性があります。Singletonでは、Singletonクラスのコンストラクタは直接初期化できないように保護されています。シングルトンにアクセスする必要があるアプリケーションは、getInstance()メソッドでシングルトンを取得する必要があります。

getInstance()で一度だけ一意のインスタンスを作成することで、システム内で一意のインスタンスが保証されます。シングルトン内の一意なインスタンスの初期化戦略には2種類あり、それぞれのコードは実装に記載されています。

効果

Singletonパターンを使用することで、システム内に1つだけのインスタンスが存在することが保証され、これは多くのサービスクラスや環境設定クラスにとって重要です。

関連ディスカッション

Singletonパターンを使用する場合、Singletonは単一のシステム内で単一のインスタンスしか保証しないことに注意することが重要です。複数のJVMで動作するEJBなどの分散成果物を使用している場合、上記の実装ではシステム全体で単一のインスタンスを維持することはできません。Singletonパターンは、システム内にインスタンスが最大でも1つしかない場合にのみ適用可能であり、避けるべきです。静的メソッドを使用するツールクラスのように、過剰に設計されたSingletonの多くは不要であり、非効率的です。Singletonの実装を見てわかるように、Singletonは継承をサポートしていません。C++のようなテンプレート技術をサポートする開発言語では、シングルトンテンプレートを定義してシングルトンを構築することができ、パターンの再利用性をさらに向上させることができます。しかし、JavaやC#などの言語では、他の実装方法しかありません。

Decorator

モデル名

Decoratorパターンは、DecoratorまたはPainterパターンとしても知られています。

解決しようとする問題

開発をしていると、あるクラスにあらかじめ用意されている機能だけでは不十分で、 クラスを拡張する必要が出てくることがよくあります。この問題を解決する最も簡単な方法は、新しいクラスを継承し、それに応じてメソッドを拡張することです。Decorator パターンは、元のクラスの上にレイヤーをラップすることで、機能拡張の問題を解決します。

モード説明

Decoratorパターンの構造を図4に示します。

デコレータコンポーネントは、コンクリートコンポーネントの装飾クラスで、同じ抽象クラスComponentを継承し、同じインターフェイスを持ちます。Concrete Componentの装飾は複数種類存在する可能性があるため、Concrete Decorator 1とConcrete Decorator 2は具体的な装飾クラスとして継承されます。この構造はクラスレベルで明確であり、静的継承よりも柔軟性があります。Decoratorクラスを使用するためのメソッドをシーケンス図を使用して図5に示します。

図4のアプローチでは、同じインターフェイスを持つoperations()が、クライアントの選択に応じて実行時に異なる動作特性を持つことができ、機能の動的な拡張が可能になります。

効果

Decorator パターンは、クラスの機能を動的に拡張できる一方で、多数のサブクラスの継承を避けることができるため、継承のアプローチよりも柔軟性が高くなります。Decoratorは動的な拡張メソッドを提供するため、特定のニーズに応じていつでも新しいデコレートクラスを生成することができます。しかし、Decoratorパターンを多用すると、似たようなインターフェイスを持つ小さな装飾オブジェクトがシステム内に大量に存在することになり、システムの保守性が低下します。

関連ディスカッション

Decoratorパターンは、クラスを動的に機能拡張する方法を提供し、幅広い応用が可能です。例えば、オープンソースプロジェクトのdisplaytagは、二次開発のためにDecorator方式で機能を拡張するインターフェイスを提供し、JDKのI/OAPIはDecoratorパターンを広範囲に利用しています。

他のパターンと同様に、Decorator の使い方を誤るとシステムの保守性が低下します。単一の場所で使用される、あるいはかなり単純な処理を行う装飾クラスを開発する場合は、Decorator パターンの必要性を検討してください。

Facade

モデル名

ファサードモードはアピアランスモードとも呼ばれ、比喩的に「ファサードモード」とも訳されます。

解決しようとする問題

プログラムでは他にもいくつかのサブシステムがよく使われます。何もしない場合、これらのサブシステムの各インターフェイスを知り、これらのインターフェイスを使用して呼び出しを行う必要があるため、システムは図6に示すようにカオス状態になります。

このような呼び出しは構造を混乱させるだけでなく、クライアントプログラムとサブシステム間の結合も大幅に増加し、拡張やメンテナンスが非常に困難になります。

モード説明

Facadeパターンの構造を図7に示します。

図7に示すように、Facadeパターンは、元のシステムの前にメソッドのレイヤーを追加し、これらのサブシステムのインタフェースをカプセル化し、外部への一貫したアクセスインタフェースを提供することによって、上記の問題を解決します。

効果

Facadeパターンは、サブシステムの詳細を保護し、クライアントプログラムがサブシステムを使用する際の複雑さを軽減します。これにより、すべての人がすべてのサブシステム・インターフェースを理解する必要性から、個々の専門家がサブシステム・インターフェースを抽象化できるように変わります。

Facadeパターンは非常に柔軟な応用が可能で、特定の実装を持ちません。 その応用の鍵は、妥当なFacadeインターフェースを抽象化することです。

関連ディスカッション

Facade パターンは、サブシステムの内部構造をカプセル化するカプセル化の考え方の良い例です。例えば、データアクセスオブジェクトは、データアクセスが特定のデータベースから分離されるようにカプセル化されることがよくあります。クライアントプログラムは、一番外側のデータベースアクセスコンポーネントへのインターフェースを知っている限り、サポートされているどのデータベースでも操作することができます。

Mediator

モデル名

メディエーター・パターンは、仲介者パターンとしても知られています。

解決しようとする問題

複雑なシステムでは、互いに通信する多くのオブジェクトがあり、オブジェクトの相互依存が発生します。オブジェクトの1つの変更は、他のオブジェクトの数に影響を与える可能性があり、システムの保守性の低下によって引き起こされるシステム内のオブジェクトの複雑なカップリングは、システムがカオスのように表示され、理解することは困難です.Mediatorは、オブジェクト間の通信のセットをカプセル化することにより、システムのためにデカップリング。

モード説明

Mediatorパターンの構造を図8に示します。

Mediator パターンでは、抽象クラス Colleague のサブクラスであるオブジェクト群は、 Mediator を継承した Mediator クラス Concrete Mediator を介して通信を行います。Mediator クラスは抽象クラス Mediator を継承し、 Mediator で定義された通信インタフェースを実装することで Mediator クラスと通信クラス間のインタフェースの整合性を確保します。

Mediatorパターンはメッセージングメカニズムと似ているように見えますが、実はメッセージングメカニズムとは大きく異なります。Mediatorはキューイングや優先順位付けなどを担当しませんが、Colleagueから送信されたメッセージに応答し、特定のColleagueクラスにメッセージを送信する必要があります。つまり、具体的なMediatorクラスはColleague間のコミュニケーションをカプセル化します。ちょうど人間の脳のように、目から送られた「目の前に落とし穴があります」というメッセージを受信すると、「前に進むのをやめてください」というメッセージを足に送ります。というメッセージを足に送ります。

効果

Mediatorはオブジェクトのグループ間の通信をカプセル化し、Colleague間の結合を減らすことで、1つのColleagueの変化が他のオブジェクトに影響を与えないようにします。脳を例にとると、歩くときには、脳は目から「前方で何かが起こっている」というメッセージを受け取り、足に「止まれ」と伝えます。脳はこのメッセージを受け取り、足に「ブレーキをかけろ」というメッセージを送ります。

しかし、Colleagueの数が多くなり、Colleague内の関係が複雑になると、MediatorクラスのConcrete Mediatorは非常に複雑になり、維持が難しくなります。

関連ディスカッション

Mediator パターンは GUI でよく使われます。フォームやダイアログボックスに複数のオブジェクトがあり、互いに通信する必要がある場合 -- たとえば、ある部門の人々を示すドロップダウンリストが、部門ドロップダウンリストのさまざまな選択に基づいて変更される必要がある場合 -- Mediator を使って通信をカプセル化することで、これらのオブジェクトの結合が減り、システムの保守性が向上します。

Mediator はしばしば非常に複雑になり、通信する必要のあるすべてのオブジェクトが Mediator の存在を知る必要があるだけでなく、Mediator は通信するすべてのオブジェクトの実装を知る必要があります。そのため、Mediator パターンは一般的に小さな範囲でしか使用されません。そうでない場合、多すぎる Colleague クラスによって引き起こされる Mediator クラスのメンテナンスの難しさは、パターンの利点を相殺し、システムをよりメンテナンスしにくくします。

Observer

モデル名

オブザーバー・パターン、別名オブザーバー・パターン。

解決しようとする問題

オブジェクトのカプセル化の特性により、一般的にシステム内のオブジェクトの状態の変化は独立しています。しかし、システム内の多くのオブジェクトは互いに関連しています。例えば、株価ティッカーシステムの場合、株価の状態の変化を複数のビューに同時に反映させる必要があります。この問題に対する最も単純な解決策は、これらの関連付けをハードコードすることです。しかし、これではシステム内のオブジェクトの結合が密になり、システムの保守や再利用が困難になります。

モード説明

オブザーバー・パターンの構造を図9に示します。

抽象クラスSubjectは、被観察者へのインターフェイスであるサブジェクトクラスを定義し、そのサブクラスConcrete Subjectは、すべてのオブザーバーであるConcreteオブザーバ

インターフェイスの一貫性を保つために、これらのオブザーバーは同じ抽象クラスObserverを継承しています。 抽象クラスはオブザーバーのリスト(オブザーバー)をSubjectに保持し、attachメソッドとdetachメソッドを通して動的に特定のオブザーバーを追加または削除します。

subscribe メソッドを使用して追加されたオブザーバーは、他のオブザーバーに気付かず、観察されているサブジェクトオブジェクトの状態変化のみを受け取ることができます。notify メソッドでは、トピッククラスは、現在サブスクライブしているオブザーバのリストに基づいて、各オブザーバに状態変更メッセージを送信します。

Concrete SubjectのsetState()メソッドは、オブジェクトの状態が変更されたことを示し、図10に示すように、notifyメソッドを呼び出してすべてのオブザーバを更新します。

効果

Observerパターンでは、具体的なオブジェクトとオブザーバの抽象的な結合が実現されます。Subjectは、現在どのオブザーバが自身の状態の変化を捕捉する必要があるかを知っていますが、それらのオブザーバが何をしようとしているかは知りません。各オブザーバは、単にオブジェクトの変化を捕捉したことを知っていますが、現在オブジェクトの状態を観測しているオブザーバが何人いるかは知りませんし、他のオブザーバに通知する必要もありません。

そのため、オブザーバを動的に追加したり削除したりするのがとても簡単になります。Observerパターンではオブジェクト間でメッセージのブロードキャストを行うことができます。しかし、ブロードキャストの副作用として、システムの負荷が増加します。 あらゆるメッセージが自動的に各オブザーバに送信され、各オブザーバはこれらのメッセージに応答しますが、必ずしも必要とは限りません。

関連ディスカッション

Observerパターンは、1対多のオブジェクトの依存関係を抽象的な結合に変換し、システムの再利用価値を高めるために広く使用されています。SmalltalkのMVCアーキテクチャは、モデルの変更をビューに渡すためにObserverパターンを適用します。

しかし、Observerの不適切な使用は多くの問題を引き起こします。図10に描かれている更新方法は、すべてのObserverに通知を送るために、setState()の後にSubjectが自動的にnofity()を実行することです。

しかし、オブジェクトの状態は常に孤立しているわけではなく、クライアントアプリケーションは setState1() メソッドの直後に setState2() メソッドを実行するかもしれません。このように、それぞれの変更はnoifty()イベントをもたらし、これらの変更は各Observerオブジェクトに増幅されます。Observerのupdate()メソッドの同時実行はプログラムエラーを引き起こす可能性が高く、update()メソッドへの排他的なアクセスはプログラムのブロックを引き起こします。

そのため、図11に示すメソッドは、オブジェクトの状態の変化の通知を送るために使われることもあります。

このアプローチは、クライアントがオブジェクトの状態を変更する一連のメソッドを実行した後にnotify()メソッドを呼び出すことで上記の問題を回避しますが、第一にオブジェクトの状態の変更が様々なオブザーバに通知されることを保証しません。

したがって、Observerパターンを使用する際には、このような問題を回避するために、オブジェクトの状態変化の依存関係を正確に定義するように注意する必要があります。前述したように、Observerを使用するとメッセージをブロードキャストすることができます。ブロードキャストは一対多のメッセージングを単純化できる諸刃の剣ですが、多くの問題ももたらします。

Observerパターンでは、ブロードキャスト展開を極力避け、update()オブジェクトでSubjectクラスの状態、特に観察しているSubjectクラスの状態を更新しないようにする必要があります。そうしないと、オブジェクト間でメッセージの無限ループが発生する可能性があります。

Intercepting Filter

モデル名

フィルターモードとも呼ばれる迎撃フィルターモード。

解決しようとする問題

Webアプリケーションの開発にMVCアーキテクチャを使用する場合、クライアントからのリクエストに対して、クライアントの認証、リクエスト元の確認、リクエストのデコードなどの前処理を行い、コントローラに渡す必要があることがよくあります。これらの前処理をコントローラに任せると、コントローラが複雑になり、保守や拡張が困難になります。

モード説明

インターセプティング・フィルター・パターンの構造を図12に示します。

FilterManager は FilterChain 全体のスケジューリングを担当し、リクエストが Target に到達する前にインターセプトして FilterChain に渡し、FilterChain 内のフィルタが順次前処理を行います。FilterChain 内のフィルタは順番に前処理を行います。 リクエストが最後のフィルタを通過するまで前処理は完了せず、 FilterManger はリクエストを実際の Target に転送します。InterceptingFilter パターンを使うときのタイミング関係を図 13 に示します。

効果

InterceptingFilterパターンを使用することで、前処理のロジックと実際の処理のロジックを分離し、実際の処理を行うTargetは特定のロジックだけを気にすればよく、リクエスト関連の前処理はFilterManagerに配置されます。同時に、これら2種類の処理の結合が切り離されるため、前処理プロセスの拡張や変更が容易になり、システムの保守性や拡張性が向上します。

関連ディスカッション

InterceptingFilterはJ2EEパターンとして文献に登場しますが、実際には幅広い応用が可能です。J2EEアプリケーションをこのパターンを使って前処理プロセスを分離して開発できるだけでなく、InterceptingFilterは.Netフレームワークのような他のWebアプリケーションにも使うことができます。

InterceptingFilterパターンを詳しく見てみると、実際にオブジェクトを扱う前に、オブジェクトを拡張する操作のレイヤーを追加するという点で、Decoratorパターンと多少似ていることがわかります。ただ、実装が大きく異なります。

Intercepting Filter をより汎用的で設定しやすいものにすることは可能です。異なるリクエストに対して異なる Filter Chain を使う必要がある場合 -- たとえばクライアントによって異なるエンコーディングに対して異なるデコーダを使う場合 -- は、Factory パターンを使って処理のための Filter オブジェクトを動的に生成することができます。他にもデザインパターンを柔軟に利用する方法はたくさんあり、様々なパターンで利用できるため、アーキテクトは実際の状況に応じて深く考える必要があります。

デザイン・パターンのまとめ

デザイン・パターンを学ぶ上で最も重要なことは、デザイン・パターンを理解することです。それぞれのデザインパターンには、内部の詳細を隠す、結合を減らすなど、優れたデザインアーキテクチャのためのアイデアが含まれています。

人間社会を観察していると、デザインパターンと同じようなことがたくさん見えてきます。人間はそれぞれ、感覚と行動を通じて外界とのコミュニケーションの媒体を提供する、よくカプセル化された物体です。人間は、視覚、聴覚、嗅覚、触覚など限られた数の感覚を通して外部からニュースを受け取りますが、これらの感覚器官は複雑な内部構造を内包しています。望遠鏡や電話などの道具を使えば、その能力を拡張することができます。複雑な人間組織には、仲介者が交流のプラットフォームを提供する間接的なコミュニケーション・チャンネルがあり、報道機関は他の人々や出来事の状況を人々に知らせます。......プロキシ・モデルに非常によく似たブローカーなど、多くの類似した例を挙げることができます。

ソフトウェア・システムの究極の目的は人間の活動を支援することであり、現実世界を抽象化することで良い設計を得ることができます。複雑な問題を解決するために、いくつかのパターンが組み合わされることがよくあります。このため、システム・アーキテクチャ設計者は、特定の問題を分析し、デザイン・パターンを包括的に使用してシステムの混乱や結合を排除し、システムの再利用性を向上させる必要があります。

特に単純なシステムでは、デザインパターンを乱用しないことが重要です。システム内のすべてのオブジェクトがファクトリーパターンで作成され、システム内のすべてのツールクラスがシングルトンとして設計され、2つのオブジェクト間の通信にメディエーターのレイヤーを追加しなければならない場合、これらはすべて望ましくありません。リファクタリングは、初期設計に加えて、システムアーキテクトがデザインパターンを徐々に適用し、リファクタリングのニーズに応じてシステムを改善し、システムの保守性と再利用性を向上させる良い機会でもあります。

Read next

山月の2020年上半期まとめ~フルスタックエンジニアになるには~|エッセイ

今年の上半期には、伝染病の影響により、北京の大学は、開かれていない、学校の門は部外者が入ることを許可されていませんので、このように中断実行長い時間に付着。北京市の罰金公園で良い免疫システムのための多くのトラブルは、公園へのスイッチで実行し、事前に年間カードを行うために予約します。 しかし、何かを行うには、常にあなたが望むものとは正反対であり、長い空に投げ当初の意図されている、私は年間カードで実行することを考えていたグリップを始めた - 北京公園 - ウールの旅。...

May 24, 2020 · 8 min read