実際のアプリケーションでブリッジ パターンを使用したことのある人はいますか? もしそうなら、あなたはそれをどのように使いましたか?それは私ですか、それとも依存関係の注入が少し混ざったアダプター パターンですか? それは本当に独自のパターンに値するのでしょうか?
13 に答える
ブリッジパターンは、「継承よりも構成を優先する」という古いアドバイスを応用したものです。互いに直交する方法で異なる時間をサブクラス化する必要がある場合に便利です。色付きの図形の階層を実装する必要があるとします。ShapeをRectangleとCircleでサブクラス化し、次にRectangleをRedRectangle、BlueRectangle、GreenRectangleでサブクラス化し、Circleでも同じようにしませんか?各シェイプには色があり、色の階層を実装すると言うことをお勧めします。これがブリッジパターンです。ええと、私は「色の階層」を実装しませんが、あなたはその考えを理解します...
いつ:
A
/ \
Aa Ab
/ \ / \
Aa1 Aa2 Ab1 Ab2
次のようにリファクタリングします。
A N
/ \ / \
Aa(N) Ab(N) 1 2
ブリッジ パターンの古典的な例は、UI 環境での形状の定義に使用されます (ブリッジ パターンに関するウィキペディアのエントリを参照してください)。Bridge パターンは、TemplateパターンとStrategyパターンを組み合わせたものです。
Bridge パターンの Adapter パターンのいくつかの側面で共通のビューです。ただし、この記事から引用するには:
一見、Bridge パターンは、ある種類のインターフェイスを別のインターフェイスに変換するためにクラスが使用されるという点で、Adapter パターンによく似ています。ただし、Adapter パターンの目的は、1 つまたは複数のクラスのインターフェイスを特定のクラスのインターフェイスと同じように見せることです。Bridge パターンは、クラスのインターフェイスをその実装から分離するように設計されているため、クライアント コードを変更せずに実装を変更または置換できます。
私の経験では、ドメイン内に 2 つの直交する次元がある場合はいつでもブリッジが解決策になるため、ブリッジは非常に頻繁に繰り返されるパターンです。たとえば、形状と描画方法、動作とプラットフォーム、ファイル形式とシリアライザーなどです。
アドバイス: 常に実装の観点からではなく、概念の観点から設計パターンを考えてください。正しい観点から見ると、Bridge を Adapter と混同することはできません。なぜなら、Bridge は別の問題を解決するからです。合成は、それ自体のためではなく、継承よりも優れていますが、直交する懸念を個別に処理できるためです。
アダプターとブリッジは確かに関連しており、その違いは微妙です。これらのパターンの1つを使用していると思っている人の中には、実際には他のパターンを使用している人もいる可能性があります。
私が見た説明は、すでに存在するいくつかの互換性のないクラスのインターフェースを統合しようとしているときにアダプターが使用されるということです。アダプタは、レガシーと見なされる可能性のある実装への一種のトランスレータとして機能します。
一方、ブリッジパターンは、グリーンフィールドである可能性が高いコードに使用されます。変更する必要のある実装に抽象的なインターフェイスを提供するようにBridgeを設計していますが、それらの実装クラスのインターフェイスも定義しています。
デバイスドライバーはBridgeのよく引用される例ですが、デバイスベンダーのインターフェイス仕様を定義している場合はブリッジだと思いますが、既存のデバイスドライバーを使用してラッパークラスを作成している場合はアダプターです。統一されたインターフェースを提供します。
したがって、コードに関しては、2つのパターンは非常に似ています。ビジネス的には、それらは異なります。
http://c2.com/cgi/wiki?BridgePatternも参照してください。
BridgeとAdapterの意図は異なり、両方のパターンが別々に必要です。
ブリッジパターン:
- 構造パターンです
- 抽象化と実装はコンパイル時にバインドされません
- 抽象化と実装 - どちらもクライアントに影響を与えずに変更できます
- 継承よりも構成を使用します。
次の場合に Bridge パターンを使用します。
- 実装の実行時バインディングが必要な場合、
- 結合されたインターフェースと多数の実装の結果、クラスが急増しています。
- 複数のオブジェクト間で実装を共有したい、
- 直交クラス階層をマップする必要があります。
@ John Sonmez の回答は、クラス階層の削減におけるブリッジ パターンの有効性を明確に示しています。
以下のドキュメント リンクを参照して、コード例でブリッジ パターンをよりよく理解することができます。
アダプタパターン:
- これにより、関連のない 2 つのインターフェイスが異なるオブジェクトを介して連携し、同じ役割を果たす可能性があります。
- 元のインターフェイスを変更します。
主な違い:
- アダプターは、設計後に物事を機能させます。ブリッジは、それらが機能する前にそれらを機能させます。
- Bridgeは、抽象化と実装を個別に変更できるように事前に設計されています。アダプターは、無関係なクラスが連携するように改造されています。
- 意図:アダプターは、2 つの無関係なインターフェースが連携できるようにします。Bridgeでは、抽象化と実装を個別に変更できます。
関連する SE の質問と UML ダイアグラムおよび作業コード:
役立つ記事:
ソースメイキングブリッジパターン記事
ソース作成アダプターパターン記事
journaldev ブリッジパターンの記事
編集:
ブリッジ パターンの実世界の例 (meta.stackoverflow.com の提案に従って、ドキュメントが日没するため、この投稿にドキュメント サイトの例を組み込みました)
ブリッジ パターンは、抽象化を実装から切り離して、両方が独立して変化できるようにします。これは、継承ではなく構成によって実現されています。
ウィキペディアのブリッジ パターン UML:
このパターンには 4 つのコンポーネントがあります。
Abstraction
: インターフェイスを定義します
RefinedAbstraction
: 抽象化を実装します:
Implementor
: 実装のためのインターフェースを定義します
ConcreteImplementor
: これは、Implementor インターフェイスを実装します。
The crux of Bridge pattern :
構成を使用した (および継承なしの) 2 つの直交クラス階層。抽象化階層と実装階層は、独立して変化する可能性があります。実装が抽象化を参照することはありません。抽象化には、メンバーとして実装インターフェイスが含まれています(構成を介して)。この構成により、継承階層がさらに 1 レベル削減されます。
実際の使用例:
さまざまな車両に、手動および自動ギア システムの両方のバージョンを搭載できるようにします。
コード例:
/* Implementor interface*/
interface Gear{
void handleGear();
}
/* Concrete Implementor - 1 */
class ManualGear implements Gear{
public void handleGear(){
System.out.println("Manual gear");
}
}
/* Concrete Implementor - 2 */
class AutoGear implements Gear{
public void handleGear(){
System.out.println("Auto gear");
}
}
/* Abstraction (abstract class) */
abstract class Vehicle {
Gear gear;
public Vehicle(Gear gear){
this.gear = gear;
}
abstract void addGear();
}
/* RefinedAbstraction - 1*/
class Car extends Vehicle{
public Car(Gear gear){
super(gear);
// initialize various other Car components to make the car
}
public void addGear(){
System.out.print("Car handles ");
gear.handleGear();
}
}
/* RefinedAbstraction - 2 */
class Truck extends Vehicle{
public Truck(Gear gear){
super(gear);
// initialize various other Truck components to make the car
}
public void addGear(){
System.out.print("Truck handles " );
gear.handleGear();
}
}
/* Client program */
public class BridgeDemo {
public static void main(String args[]){
Gear gear = new ManualGear();
Vehicle vehicle = new Car(gear);
vehicle.addGear();
gear = new AutoGear();
vehicle = new Car(gear);
vehicle.addGear();
gear = new ManualGear();
vehicle = new Truck(gear);
vehicle.addGear();
gear = new AutoGear();
vehicle = new Truck(gear);
vehicle.addGear();
}
}
出力:
Car handles Manual gear
Car handles Auto gear
Truck handles Manual gear
Truck handles Auto gear
説明:
Vehicle
は抽象化です。Car
とTruck
は の 2 つの具体的な実装ですVehicle
。Vehicle
抽象メソッドを定義します:addGear()
.Gear
実装者インターフェースですManualGear
およびAutoGear
の 2 つの実装です。Gear
Vehicle
implementor
インターフェイスを実装するのではなく、インターフェイスを含みます。Compositon
実装者インターフェイスの重要性は、このパターンの核心です。これにより、抽象化と実装を個別に変更できます。Car
抽象化Truck
のための実装 (再定義された抽象化) を定義します :addGear()
: 含まれますGear
- いずれかManual
またはAuto
ブリッジ パターンの使用例:
- 抽象化と実装は互いに独立して変更でき、コンパイル時にバインドされません
- 直交階層のマッピング - 1 つは抽象化用、もう 1 つは実装用です。
仕事でブリッジパターンを使用しました。私はC++でプログラムします。ここでは、PIMPLイディオム(実装へのポインター)と呼ばれることがよくあります。次のようになります。
class A
{
public:
void foo()
{
pImpl->foo();
}
private:
Aimpl *pImpl;
};
class Aimpl
{
public:
void foo();
void bar();
};
この例class A
には、インターフェースとclass Aimpl
実装が含まれています。
このパターンの使用法の1つは、実装クラスのパブリックメンバーの一部のみを公開し、他のメンバーは公開しないことです。この例でAimpl::foo()
は、のパブリックインターフェイスを介してのみ呼び出すことができますがA
、Aimpl::bar()
もう1つの利点はAimpl
、のユーザーがインクルードする必要のない別のヘッダーファイルで定義できることですA
。あなたがしなければならないのは、定義されるAimpl
前の前方宣言を使用し、A
参照しているすべてのメンバー関数の定義をpImpl
.cppファイルに移動することです。Aimpl
これにより、ヘッダーを非公開に保ち、コンパイル時間を短縮することができます。
形状の例をコードに入れるには:
#include<iostream>
#include<string>
#include<cstdlib>
using namespace std;
class IColor
{
public:
virtual string Color() = 0;
};
class RedColor: public IColor
{
public:
string Color()
{
return "of Red Color";
}
};
class BlueColor: public IColor
{
public:
string Color()
{
return "of Blue Color";
}
};
class IShape
{
public:
virtual string Draw() = 0;
};
class Circle: public IShape
{
IColor* impl;
public:
Circle(IColor *obj):impl(obj){}
string Draw()
{
return "Drawn a Circle "+ impl->Color();
}
};
class Square: public IShape
{
IColor* impl;
public:
Square(IColor *obj):impl(obj){}
string Draw()
{
return "Drawn a Square "+ impl->Color();;
}
};
int main()
{
IColor* red = new RedColor();
IColor* blue = new BlueColor();
IShape* sq = new Square(red);
IShape* cr = new Circle(blue);
cout<<"\n"<<sq->Draw();
cout<<"\n"<<cr->Draw();
delete red;
delete blue;
return 1;
}
出力は次のとおりです。
Drawn a Square of Red Color
Drawn a Circle of Blue Color
順列によるサブクラスの爆発につながることなく、新しい色や形をシステムに簡単に追加できることに注意してください。
私にとっては、インターフェイスを交換できるメカニズムと考えています。現実の世界では、複数のインターフェイスを使用できるクラスがある場合がありますが、Bridge を使用すると交換できます。
同じ古い形状と色の例のボードを入手した場合は、ブリッジ パターンの新しい例を 1 つ示します。
カード決済やネットバンキングなど、支払い方法が異なるとします。また、CITI 銀行や HSBC 銀行など、さまざまな支払いゲートウェイがあります。
次に、支払いゲートウェイ メンバーを支払いモードに追加するだけです。そして、実行時にこの情報を支払いモード オブジェクトに渡します。そして、支払いを行います。
たとえば、CITI 銀行支払いゲートウェイでカード支払いを行います。