私はプロキシパターンを見ていましたが、私には、デコレータ、アダプタ、およびブリッジパターンのようにひどいように見えます。私は何かを誤解していますか?違いは何ですか?なぜ他のものに対してプロキシパターンを使用するのですか?過去に実際のプロジェクトでそれらをどのように使用しましたか?
私はプロキシパターンを見ていましたが、私には、デコレータ、アダプタ、およびブリッジパターンのようにひどいように見えます。私は何かを誤解していますか?違いは何ですか?なぜ他のものに対してプロキシパターンを使用するのですか?過去に実際のプロジェクトでそれらをどのように使用しましたか?
回答:
プロキシ、デコレータ、アダプタ、およびブリッジはすべて、クラスの「ラッピング」のバリエーションです。しかし、それらの用途は異なります。
プロキシは、オブジェクトを遅延インスタンス化したり、リモートサービスを呼び出していることを隠したり、オブジェクトへのアクセスを制御したりする場合に使用できます。
デコレータは「スマートプロキシ」とも呼ばれます。これは、機能をオブジェクトに追加するときに使用されますが、そのオブジェクトのタイプを拡張することによっては使用されません。これにより、実行時にそうすることができます。
アダプターは、抽象インターフェースがあり、そのインターフェースを、機能が似ているがインターフェースが異なる別のオブジェクトにマップする場合に使用されます。
ブリッジはアダプターに非常に似ていますが、抽象インターフェースと基礎となる実装の両方を定義するときに、これをブリッジと呼びます。つまり、レガシーコードやサードパーティのコードに適応するのではなく、すべてのコードの設計者ですが、異なる実装を交換できる必要があります。
Facadeは、1つまたは複数のクラスのサブシステムへの上位レベル(読み取り:より単純)のインターフェースです。複数のオブジェクトを表す必要がある複雑な概念があるとします。そのオブジェクトのセットに変更を加えると、どのオブジェクトに呼び出す必要のあるメソッドがあるかが常にわかるとは限らないため、混乱を招きます。これが、オブジェクトのコレクションに対して実行できるすべての複雑な操作に高レベルのメソッドを提供するFacadeを書くときです。例:学校のセクションのためのドメインモデル、のような方法でcountStudents()
、reportAttendance()
、assignSubstituteTeacher()
、など。
その構造もそうです。
プロキシとデコレータはどちらも、ラップされた型と同じインターフェースを持っていますが、プロキシは内部でインスタンスを作成しますが、デコレータはコンストラクタでインスタンスを取得します。
アダプターとファサードはどちらも、ラップするインターフェースとは異なるインターフェースを持っています。ただし、アダプターは既存のインターフェースから派生しますが、ファサードは新しいインターフェースを作成します。
ブリッジとアダプターどちらも既存のタイプを指しています。ただし、ブリッジは抽象型を指し、アダプタは具象型を指す場合があります。ブリッジでは、実行時に実装をペアリングできますが、アダプターは通常ペアリングできません。
主題についての私の見解。
4つのパターンはすべて共通点が多く、4つすべてが非公式にラッパーまたはラッパーパターンと呼ばれることもあります。すべてがコンポジションを使用し、サブジェクトをラップし、ある時点で実行をサブジェクトに委任して、1つのメソッド呼び出しを別のメソッド呼び出しにマッピングします。彼らはクライアントに別のオブジェクトを構築し、すべての関連データをコピーする必要性をなくします。賢く使用すれば、メモリとプロセッサを節約できます。
疎結合を促進することにより、安定したコードが避けられない変更にさらされることが少なくなり、他の開発者が読みやすくなります。
アダプタ
アダプターは、サブジェクト(アダプター)を別のインターフェースに適応させます。このようにして、名目上異なるタイプのコレクションにオブジェクトを配置できます。
アダプターは関連するメソッドのみをクライアントに公開し、他のすべてを制限し、外部ライブラリの適応など、特定のコンテキストの使用意図を明らかにし、一般的ではなく、アプリケーションのニーズに重点を置くようにします。アダプターは、コードの読みやすさと自己記述を向上させます。
アダプターは、他のチームの揮発性コードから1つのチームを保護します。オフショアチームを扱うときの救世主ツール;-)
あまり言及されていない目的は、サブジェクトクラスが過剰な注釈を防ぐことです。アノテーションに基づく非常に多くのフレームワークでは、これはかつてないほど重要な使用法になります。
アダプターは、単一継承のみのJava制限を回避するのに役立ちます。複数の継承の印象を与える1つのエンベロープの下に複数のアダプティを組み合わせることができます。
コード的には、アダプターは「シン」です。アダプティメソッドを呼び出すだけでなく、そのような呼び出しを行うために必要なデータ変換を時折行うだけでなく、アダプティクラスに多くのコードを追加するべきではありません。
JDKや基本的なライブラリには、良いアダプタの例はあまりありません。アプリケーション開発者は、アダプターを作成して、ライブラリーをアプリケーション固有のインターフェースに適合させます。
デコレータ
デコレータは、デリゲートだけでなく、1つのメソッドを別のメソッドにマップするだけでなく、さらに多くの機能を提供し、いくつかのサブジェクトメソッドの動作を変更します。サブジェクトメソッドをまったく呼び出さず、別のオブジェクト、ヘルパーオブジェクトにデリゲートします。
デコレーターは通常、ロギング、暗号化、フォーマット、サブジェクトへの圧縮などのラップされたオブジェクトに(透過的に)機能を追加します。この新しい機能は、多くの新しいコードをもたらす可能性があります。したがって、デコレータは通常、アダプタよりもはるかに「太い」です。
デコレータは、サブジェクトのインターフェースのサブクラスでなければなりません。サブジェクトの代わりに透過的に使用できます。BufferedOutputStreamを参照してください。これは引き続きOutputStreamであり、そのまま使用できます。これがアダプタとの大きな技術的な違いです。
デコレータファミリ全体のテキストブックの例は、JDK(Java IO)に含まれています。BufferedOutputStream、FilterOutputStream、ObjectOutputStreamなどのすべてのクラスは、OutputStreamのデコレータです。。彼らはタマネギを重ねることができ、一つのデコレーターが再び装飾され、より多くの機能を追加します。
代理
プロキシは一般的なラッパーではありません。ラップされたオブジェクトであるプロキシサブジェクトは、プロキシ作成時にまだ存在していない可能性があります。多くの場合、プロキシは内部で作成します。これは、オンデマンドで作成された重いオブジェクトであるか、異なるJVMまたは異なるネットワークノードのリモートオブジェクトであり、ネイティブコードのコンポーネントである非Javaオブジェクトでさえあります。必要なラップや別のオブジェクトへの委譲はまったく必要ありません。
最も典型的な例は、リモートプロキシ、重いオブジェクト初期化子、アクセスプロキシです。
リモートプロキシ-サブジェクトはリモートサーバー、異なるJVM、または非Javaシステムにさえあります。プロキシは、メソッド呼び出しをRMI / REST / SOAP呼び出しまたは必要なものに変換し、基盤となるテクノロジーへの露出からクライアントを保護します。
Lazy Load Proxy –最初の使用または最初の集中的な使用のみでオブジェクトを完全に初期化します。
アクセスプロキシ-サブジェクトへのアクセスを制御します。
ファサード
ファサードは、最小知識の設計原則(デメテルの法則)と密接に関連しています。ファサードはアダプターに非常に似ています。どちらもラップし、1つのオブジェクトを別のオブジェクトにマップしますが、目的は異なります。ファサードは、サブジェクトの複雑な構造、複雑なオブジェクトグラフを平坦化し、複雑な構造へのアクセスを簡素化します。
Facadeは複雑な構造をラップし、フラットなインターフェイスを提供します。これにより、クライアントオブジェクトがサブジェクト構造の内部関係にさらされることがなくなり、疎結合が促進されます。
ブリッジ
アダプターパターンのより複雑なバリアントで、実装だけでなく抽象化も異なります。委任にもう1つの間接指定が追加されます。追加の委任はブリッジです。それは、インターフェースの適応からでもアダプターを切り離します。他のラッピングパターンよりも複雑さが増すため、注意して適用してください。
コンストラクターの違い
コンストラクタを見ると、パターンの違いも明らかです。
プロキシは既存のオブジェクトをラップしていません。コンストラクターにサブジェクトはありません。
デコレーターとアダプターは、既存のオブジェクトをラップしますが、通常
はコンストラクターで提供されます。
Facadeコンストラクターは、オブジェクトグラフ全体のルート要素を取得します。それ以外の場合、アダプターと同じように見えます。
実際の例– JAXB Marshalling Adapter。このアダプターの目的は、単純なフラットクラスを外部で必要なより複雑な構造にマッピングし、過剰な注釈でサブジェクトクラスを「汚染」するのを防ぐことです。
GoFパターンの多くには多くの重複があります。それらはすべてポリモーフィズムの力に基づいて構築されており、意図が実際に異なるだけの場合もあります。(戦略vs状態)
Head First Design Patternsを読んだ後、パターンに対する理解が100倍になりました。
おすすめです!
専門家からの良い答えはすべて、各パターンが何を表すのかをすでに説明しています。
要所を飾ります。
デコレータ:
例(チェーンあり):&インターフェースにjava.io
関連するパッケージクラスInputStream
OutputStream
FileOutputStream fos1 = new FileOutputStream("data1.txt");
ObjectOutputStream out1 = new ObjectOutputStream(fos1);
プロキシ:
例:java.rmi
パッケージクラス。
アダプタ:
例java.io.InputStreamReader
(をInputStream
返すReader
)
ブリッジ:
例:のコレクションクラスjava.util
。List
によって実装されましたArrayList
。
キーノート:
さまざまなデザインパターンの例に関するSEの質問/記事をご覧ください
それらは非常によく似ており、それらの間の線はかなり灰色です。プロキシパターンとデコレータパターンを読むことをお勧めしますc2 wikiエントリ。
そこのエントリーとディスカッションはかなり広範囲であり、それらは他の関連記事にもリンクしています。ちなみに、c2 wikiは、異なるパターン間のニュアンスについて疑問に思うときに優れています。
c2エントリを要約すると、デコレータが動作を追加/変更することになりますが、プロキシはアクセス制御(遅延インスタンス化、リモートアクセス、セキュリティなど)とさらに関係があります。しかし、私が言ったように、それらの間の線は灰色であり、簡単にデコレータと見なすことができるプロキシへの参照、およびその逆を参照しています。
4つのパターンはすべて、内部オブジェクト/クラスを外部パターンでラップする必要があるため、構造的に非常に似ています。私は目的によって違いを概説します:
そして、内部オブジェクトと外部オブジェクトの間のインターフェイスのバリエーションによって:
これはHead First Design Patternsからの引用です
定義は本に属しています。例は私に属しています。
デコレータ -インターフェイスを変更しませんが、責任を追加します。車のインターフェースがあると仮定します。これを車の異なるモデル(s、sv、sl)に実装する場合、一部のモデルにさらに責任を追加する必要がある場合があります。サンルーフ、エアバッグなどがあります。
アダプター -あるインターフェースを別のインターフェースに変換します。あなたは車のインターフェースを持っていて、ジープのように振る舞うことを望んでいます。だからあなたは車を持って、それを修正してジープに変えます。本物のジープではないので。しかしジープのように振る舞います。
ファサード -インターフェースをシンプルにします。車、飛行機、船のインターフェースがあるとします。実際に必要なのは、ある場所から別の場所に人を送るクラスだけです。使用する車両をファサードで決定する必要があります。次に、それらすべてを収集しますインターフェース参照を1つの傘の下に、単純に保つように決定/委任させます。
ヘッドファースト:「ファサードはインターフェースを簡素化するだけでなく、コンポーネントのサブシステムからクライアントを切り離します。ファサードとアダプターは複数のクラスをラップする可能性がありますが、ファサードの目的は単純化することですが、アダプターはインターフェースを別のものに変換することです。 」
私はウェブサービスを利用する際によく利用します。プロキシパターンは、おそらく「ラッパーパターン」などのより実用的な名前に変更する必要があります。MSExcelのプロキシであるライブラリもあります。これにより、Excelなどの背景の詳細を気にすることなく、Excelを非常に簡単に自動化できます。バージョンがインストールされている(存在する場合)。
詳細な実装について言えば、私はプロキシとデコレータ、アダプタ、ファサードの違いを見つけます...これらのパターンの一般的な実装では、囲んでいるオブジェクトによってラップされたターゲットオブジェクトがあります。クライアントは、ターゲットオブジェクトではなく、囲んでいるオブジェクトを使用します。そして実際には、ターゲットオブジェクトは、オブジェクトを囲むいくつかのメソッド内で重要な役割を果たします。
ただし、プロキシの場合、囲んでいるオブジェクトはいくつかのメソッドを単独で再生できます。ターゲットオブジェクトが参加する必要があるいくつかのメソッドをクライアントが呼び出すと、ターゲットオブジェクトが初期化されます。これは遅延初期化です。他のパターンの場合、囲んでいるオブジェクトは実質的にターゲットオブジェクトに基づいています。そのため、ターゲットオブジェクトは常に、コンストラクター/セッターで囲んでいるオブジェクトと共に初期化されます。
もう1つは、プロキシがターゲットの機能を正確に実行するのに対し、他のパターンはターゲットに機能を追加することです。
Bill Karwingの回答に例を追加したいと思います(これは素晴らしいことです)。実装の重要な違いもいくつか追加しています。
引用部分は[ https://stackoverflow.com/a/350471/1984346](Bill Karwing)の回答から引用
プロキシ、デコレータ、アダプタ、およびブリッジはすべて、クラスの「ラッピング」のバリエーションです。しかし、それらの用途は異なります。
- プロキシは、オブジェクトを遅延インスタンス化したり、リモートサービスを呼び出していることを隠したり、オブジェクトへのアクセスを制御したりする場合に使用できます。
プロキシされるProxyClassとObjectClassは同じインターフェースを実装する必要があるため、交換可能です
例-高価なオブジェクトのプロキシ
class ProxyHumanGenome implements GenomeInterface {
private $humanGenome = NULL;
// humanGenome class is not instantiated at construct time
function __construct() {
}
function getGenomeCount() {
if (NULL == $this->humanGenome) {
$this->instantiateGenomeClass();
}
return $this->humanGenome->getGenomeCount();
}
}
class HumanGenome implement GenomeInterface { ... }
- デコレータは「スマートプロキシ」とも呼ばれます。これは、機能をオブジェクトに追加するときに使用されますが、そのオブジェクトのタイプを拡張することによっては使用されません。これにより、実行時にそうすることができます。
DecoratorClassは、ObjectClassの拡張インターフェースを実装する必要があります。したがって、ObjectClassはDecoratorClassに置き換えることができますが、その逆はできません。
例-追加機能の追加
class DecoratorHumanGenome implements CheckGenomeInterface {
// ... same code as previous example
// added functionality
public function isComplete() {
$this->humanGenome->getCount >= 21000
}
}
interface CheckGenomeInterface extends GenomeInterface {
public function isComplete();
}
class HumanGenome implement GenomeInterface { ... }
- アダプターは、抽象インターフェースがあり、そのインターフェースを、機能が似ているがインターフェースが異なる別のオブジェクトにマップする場合に使用されます。
実装の違いプロキシ、デコレータ、アダプタ
アダプターは、そのサブジェクトに別のインターフェースを提供します。プロキシは同じインターフェースを提供します。Decoratorは拡張されたインターフェースを提供します。
ブリッジはアダプターに非常に似ていますが、抽象インターフェースと基礎となる実装の両方を定義するときに、これをブリッジと呼びます。つまり、レガシーコードやサードパーティのコードに適応するのではなく、すべてのコードの設計者ですが、さまざまな実装を交換できる必要があります。
Facadeは、1つまたは複数のクラスのサブシステムへの上位レベル(読み取り:より単純)のインターフェースです。複数のオブジェクトを表す必要がある複雑な概念があるとします。そのオブジェクトのセットに変更を加えると、どのオブジェクトに呼び出す必要のあるメソッドがあるかが常にわかるとは限らないため、混乱を招きます。これが、オブジェクトのコレクションに対して実行できるすべての複雑な操作に高レベルのメソッドを提供するFacadeを書くときです。例:学校のセクションのためのドメインモデル、のような方法で
countStudents()
、reportAttendance()
、assignSubstituteTeacher()
、など。
この回答のほとんどの情報はhttps://sourcemaking.com/design_patternsからのものです。デザインパターンの優れたリソースとしてお勧めします。
コードは明確なアイデアを提供すると信じています(他の回答も補完するため)。以下を参照してください(クラスが実装およびラップするタイプに注目してください)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TestConsole
{
class Program
{
static void Main(string[] args)
{
/* Proxy */
Console.WriteLine(Environment.NewLine);
Console.WriteLine("PROXY");
Console.WriteLine(Environment.NewLine);
//instead of creating here create using a factory method, the facory method will return the proxy
IReal realProxy = new RealProxy();
Console.WriteLine("calling do work with the proxy object ");
realProxy.DoWork();
Console.WriteLine(Environment.NewLine);
Console.WriteLine("ADAPTER");
Console.WriteLine(Environment.NewLine);
/*Adapter*/
IInHand objectIHave = new InHand();
Api myApi = new Api();
//myApi.SomeApi(objectIHave); /*I cant do this, use a adapter then */
IActual myAdaptedObject = new ActualAdapterForInHand(objectIHave);
Console.WriteLine("calling api with my adapted obj");
myApi.SomeApi(myAdaptedObject);
Console.WriteLine(Environment.NewLine);
Console.WriteLine("DECORATOR");
Console.WriteLine(Environment.NewLine);
/*Decorator*/
IReady maleReady = new Male();
Console.WriteLine("now male is going to get ready himself");
maleReady.GetReady();
Console.WriteLine(Environment.NewLine);
IReady femaleReady = new Female();
Console.WriteLine("now female is going to get ready her self");
femaleReady.GetReady();
Console.WriteLine(Environment.NewLine);
IReady maleReadyByBeautician = new Beautician(maleReady);
Console.WriteLine("now male is going to get ready by beautician");
maleReadyByBeautician.GetReady();
Console.WriteLine(Environment.NewLine);
IReady femaleReadyByBeautician = new Beautician(femaleReady);
Console.WriteLine("now female is going to get ready by beautician");
femaleReadyByBeautician.GetReady();
Console.WriteLine(Environment.NewLine);
Console.ReadLine();
}
}
/*Proxy*/
public interface IReal
{
void DoWork();
}
public class Real : IReal
{
public void DoWork()
{
Console.WriteLine("real is doing work ");
}
}
public class RealProxy : IReal
{
IReal real = new Real();
public void DoWork()
{
real.DoWork();
}
}
/*Adapter*/
public interface IActual
{
void DoWork();
}
public class Api
{
public void SomeApi(IActual actual)
{
actual.DoWork();
}
}
public interface IInHand
{
void DoWorkDifferently();
}
public class InHand : IInHand
{
public void DoWorkDifferently()
{
Console.WriteLine("doing work slightly different ");
}
}
public class ActualAdapterForInHand : IActual
{
IInHand hand = null;
public ActualAdapterForInHand()
{
hand = new InHand();
}
public ActualAdapterForInHand(IInHand hnd)
{
hand = hnd;
}
public void DoWork()
{
hand.DoWorkDifferently();
}
}
/*Decorator*/
public interface IReady
{
void GetReady();
}
public class Male : IReady
{
public void GetReady()
{
Console.WriteLine("Taking bath.. ");
Console.WriteLine("Dress up....");
}
}
public class Female : IReady
{
public void GetReady()
{
Console.WriteLine("Taking bath.. ");
Console.WriteLine("Dress up....");
Console.WriteLine("Make up....");
}
}
//this is a decorator
public class Beautician : IReady
{
IReady ready = null;
public Beautician(IReady rdy)
{
ready = rdy;
}
public void GetReady()
{
ready.GetReady();
Console.WriteLine("Style hair ");
if (ready is Female)
{
for (int i = 1; i <= 10; i++)
{
Console.WriteLine("doing ready process " + i);
}
}
}
}
}
デザインパターンは数学ではなく、アートとソフトウェアエンジニアリングの組み合わせです。この要件のように、プロキシ、ブリッジなどを使用する必要はありません。問題を解決するためにデザインパターンが作成されます。設計上の問題が予想される場合は、それを使用してください。経験に基づいて、特定の問題、どのパターンを使用するかを知るようになります。確かな設計原則が得意であれば、パターンであることを知らずに設計パターンを実装することになります。一般的な例は、統計と工場のパターンです
したがって、堅固な設計原則、クリーンなコーディング原則、およびttdにさらに集中してください。