いつブリッジパターンを使用しますか?アダプターパターンとどう違うのですか?


154

誰もがブリッジパターンを実際のアプリケーションで使用したことがありますか?もしそうなら、それをどのように使用しましたか?それは私ですか、それとも少しの依存関係の注入がミックスされたアダプタパターンだけですか?それは本当に独自のパターンに値するのでしょうか?


この質問に対する別の回答を受け入れることを検討してください。現在受け入れられている答えは正しくなく、役に立ちません。新しい答えははるかに優れています。
jaco0646

GoF本では、直接、この質問に答えます。
jaco0646

回答:


76

ブリッジパターンの古典的な例は、UI環境での形状の定義に使用されます(ブリッジパターンウィキペディアエントリを参照)。ブリッジパターンがある複合テンプレート戦略パターン。

これは、BridgeパターンのAdapterパターンのいくつかの側面の一般的なビューです。ただし、この記事から引用するには:

一見すると、クラスを使用してインターフェイスの種類を別の種類に変換するという点で、ブリッジパターンはアダプタパターンとよく似ています。ただし、Adapterパターンの目的は、1つ以上のクラスのインターフェースを特定のクラスのインターフェースと同じに見せることです。Bridgeパターンは、クラスのインターフェースを実装から分離するように設計されているため、クライアントコードを変更せずに実装を変更または置換できます。


1
Bridgeは、テンプレートや戦略とは関係ありません。橋は構造パターンです。テンプレートと戦略は行動パターンです。
jaco0646

249

フェデリコジョンの答え組み合わせがあります。

いつ:

                   ----Shape---
                  /            \
         Rectangle              Circle
        /         \            /      \
BlueRectangle  RedRectangle BlueCircle RedCircle

リファクタリング:

          ----Shape---                        Color
         /            \                       /   \
Rectangle(Color)   Circle(Color)           Blue   Red

6
なぜ色を継承するのですか?
vainolo 2013

10
@vainolo(色はインターフェイスであり、青、赤は具体的な色であるため)
Weltschmerz

3
これは単なるリファクタリングです。ブリッジパターンの意図:「抽象化を実装から分離して、2つを独立して変更できるようにする」抽象化はどこにあり、実装はどこにありますか?
clapas 2017

1
Rectangle(Color)は、BlueRectangleのものより抽象的なものではありませんか?
Anton Shchastnyi 2017

2
@clapas、抽象化はプロパティの "Shape.color"であるため、クラスRedとクラスBlueが実装され、Colorインターフェースはブリッジです。
reco

230

Bridgeパターンは、「継承よりも構成を優先する」という古いアドバイスを適用したものです。互いに直交する方法で異なる時間をサブクラス化する必要がある場合に便利です。色付きの形状の階層を実装する必要があるとしましょう。ShapeをRectangleとCircleでサブクラス化してから、RectangleをRedRectangle、BlueRectangle、GreenRectangleでサブクラス化し、Circleについても同じようにしませんか?各Shape はColorがあり、色の階層を実装すると言います。それがブリッジパターンです。ええと、私は「色の階層」を実装するつもりはありませんが、あなたは考えを理解しています...


1
この説明の図解については、以下のAnton Shchastnyi図も参照してください。
NomadeNumerique 14

2
色は実装階層の良い例ではないと思いますが、やや混乱しています。GoFによる「設計パターン」のブリッジパターンの良い例があり、実装はプラットフォームに依存しています。IBMのPM、UNIXのXなど
clapas

215

いつ:

        A
     /     \
    Aa      Ab
   / \     /  \
 Aa1 Aa2  Ab1 Ab2

リファクタリング:

     A         N
  /     \     / \
Aa(N) Ab(N)  1   2

3
私はそれがパターンへの非常に実用的なアプローチだと思います:1)次善の簡単な設計を説明する2)より良い因数分解されたものに設計/コードをリファクタリングする
Alexey

1
数学の概念を使用して、ブリッジの設計パターンを説明します。興味深い。
Jian Huang

1
これは単なるリファクタリングです。ブリッジパターンの意図:「抽象化を実装から分離して、2つを独立して変更できるようにする」抽象化はどこにあり、実装はどこにありますか?
clapas 2017

ジョンはそれをブログの投稿にうまく載せています。高レベルの概要を読むのに適しています。
Vaibhav Bhalla

29

アダプターとブリッジは確かに関連しており、その区別は微妙です。これらのパターンの1つを使用していると考える一部の人々は、実際には他のパターンを使用している可能性があります。

私が見た説明は、すでに存在するいくつかの互換性のないクラスのインターフェースを統一しようとしているときにアダプタが使用されるということです。アダプタは、レガシーと見なすことができる実装への一種のトランスレータとして機能します

一方、グリーンパターンである可能性が高いコードには、ブリッジパターンが使用されます。変更する必要がある実装に抽象インターフェースを提供するようにブリッジを設計していますが、それらの実装クラスのインターフェースも定義します。

デバイスドライバーはBridgeのよく引用される例ですが、デバイスベンダーのインターフェイス仕様を定義している場合はBridgeですが、既存のデバイスドライバーを使用してラッパークラスを作成している場合はBridgeです統一されたインターフェースを提供します。

したがって、コード的には、2つのパターンは非常によく似ています。ビジネス的には、それらは異なります。

http://c2.com/cgi/wiki?BridgePatternも参照してください。


おいビル。デバイスドライバーでブリッジパターンを使用する必要がある理由がわかりません。つまり、実装は(読み取り、書き込み、シークなどの)実装を簡単に適切なクラスにポリモーフィズムを介して委任できるということですか?それともビジターと?なぜ橋でなければならないのですか?前もって感謝します。
stdout 2016年

1
@zgulser、はい、多態性を使用します。Bridgeパターンは、抽象化から実装を切り離すためのサブクラスの使用法の1つを記述しています。
Bill Karwin、2016年

さあ、今日の色の抽象化からShapeの実装(つまり、Rectangle)を分離するつもりでしたか?それを行うにはさまざまな方法があるとおっしゃっていると思いますが、Bridgeはその1つにすぎません。
stdout 2016

はい、サブクラス化には他の用途があります。サブクラスを使用するこの特定の方法は、それをブリッジパターンにするものです。
ビル・カーウィン、

そして、私が意味する分離は、抽象的なShapeインターフェースから具体的なRectangle実装へのものです。したがって、具象オブジェクトは実際にはShapeの一部のサブクラスですが、「Shape」タイプのオブジェクトを必要とするコードを記述できます。
Bill Karwin、2016

27

私の経験では、Bridgeは非常に頻繁に繰り返されるパターンです。これは、ドメインに2つの直交する次元がある場合のソリューションであるためです。たとえば、形状と描画方法、動作とプラットフォーム、ファイル形式とシリアライザなど。

そしてアドバイス:デザインパターンは、実装の観点からではなく、概念的な観点から常に考えてください。適切な観点から見ると、Bridgeはアダプターと混同することはできません。これらは別の問題を解決し、コンポジションは継承よりも優れているためではなく、直交する問題を個別に処理できるためです。


22

BridgeAdapterの目的は異なり、両方のパターンが個別に必要です。

ブリッジパターン:

  1. 構造パターンです
  2. 抽象化と実装はコンパイル時に拘束されない
  3. 抽象化と実装-どちらもクライアントに影響を与えることなく変更できます
  4. 継承よりも構成を使用します。

Bridgeパターンは次の場合に使用します。

  1. 実装のランタイムバインディングが必要な場合
  2. あなたは、結合されたインターフェースと多数の実装から生じるクラスの急増を持っています、
  3. 複数のオブジェクト間で実装を共有したい、
  4. 直交クラス階層をマップする必要があります。

@ John Sonmezの回答は、クラス階層の削減におけるブリッジパターンの有効性を明確に示しています。

以下のドキュメントリンクを参照すると、コード例を使用してブリッジパターンをより深く理解できます。

アダプターパターン

  1. これにより、2つの無関係なインターフェースが異なるオブジェクトを介して連携し、おそらく同じ役割を果たします。
  2. 元のインターフェースを変更します。

主な違い:

  1. アダプターは、設計後に機能します。ブリッジは、それらが動作する前にそれらを動作させます。
  2. Bridgeは、抽象化と実装を個別に変更できるように事前に設計されていますアダプターは、関連のないクラスが一緒に機能するように改良されました。
  3. 意図:アダプターは、2つの無関係なインターフェースが一緒に機能することを可能にします。ブリッジを使用すると、抽象化と実装を個別に変更できます。

UMLダイアグラムと作業コードを含む関連するSEの質問:

ブリッジパターンとアダプターパターンの違い

役立つ記事:

ソースメイキングブリッジパターン記事

ソースメイキングアダプターパターン記事

journaldevブリッジパターンの記事

編集:

ブリッジパターンの実世界の例(meta.stackoverflow.comの提案に従って、ドキュメントが沈むため、この投稿にドキュメントサイトの例を組み込んだ)

ブリッジパターンは、抽象化を実装から切り離し、両方を独立して変更できるようにします。それは継承ではなく構成で達成されました。

ウィキペディアのブリッジパターンUML:

ウィキペディアのブリッジパターン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

説明:

  1. Vehicle 抽象化です。
  2. CarおよびのTruck2つの具体的な実装ですVehicle
  3. Vehicle抽象メソッドを定義しますaddGear()
  4. Gear 実装者インターフェースです
  5. ManualGearとのAutoGear2つの実装です Gear
  6. Vehicleimplementorインターフェースを実装するのではなく、インターフェースを含みます。Compositon実装者インターフェースのこのパターンの核心です:それは抽象化と実装が独立して変化することを可能にします。
  7. Carそして、Truck抽象化のための実装(再定義された抽象化)を定義しますaddGear()::含まれています Gear- ManualまたはAuto

ブリッジパターンの使用例

  1. 抽象化実装は互いに独立して変更でき、コンパイル時にバインドされません
  2. 直交階層のマップ-1つは抽象化用、もう1つは実装用

「アダプターは、設計後に物事を機能させます。ブリッジは、設計する前に物事を機能させます。」Pluggable Adapterを調べてください。これは、GoFがデザインパターンブックの「アダプター」セクションで説明したアダプターのバリエーションです。目的は、まだ存在しないクラスのインターフェースを作成することです。プラグ可能なアダプタはブリッジではないので、最初のポイントが有効だとは思わない。
c1moore 2016年

手動と自動のギアではトラックと車で異なる実装が必要になる場合があります
andigor

9

私は仕事でブリッジパターンを使用しました。私は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()は、のパブリックインターフェースを介してのみ呼び出すことができますがAAimpl::bar()

もう1つの利点はAimpl、のユーザーが含める必要のない別のヘッダーファイルで定義できることですA。あなたがしなければならないのは、Aimplbefore Aが定義されているという前方宣言を使用し、参照pImplしているすべてのメンバー関数の定義を.cppファイルに移動することだけです。これにより、Aimplヘッダーをプライベートに保ち、コンパイル時間を短縮できます。


2
このパターンを使用する場合、AImplにはヘッダーも必要ありません。Aクラスの実装ファイルにインラインで配置しただけです
1800情報

作成者は非公開です。これに関して新しい質問があります。stackoverflow.com
Roland

7

シェイプの例をコードに含めるには:

#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

順列によるサブクラスの爆発につながることなく、新しい色と形状をシステムに簡単に追加できることに注意してください。


0

私にとっては、インターフェースを交換できるメカニズムと考えています。現実の世界では、複数のインターフェースを使用できるクラスがあるかもしれませんが、Bridgeを使用すると交換できます。


0

あなたは保険会社で働いており、さまざまな種類のタスク(会計、契約、請求など)を管理するワークフローアプリケーションを開発しています。これが抽象化です。実装側では、電子メール、ファックス、電子メッセージングなどのさまざまなソースからタスクを作成できる必要があります。

これらのクラスから設計を開始します。

public class Task {...}
public class AccountingTask : Task {...}
public class ContractTask : Task {...}
public class ClaimTask : Task {...}

ここで、各ソースを特定の方法で処理する必要があるため、各タスクタイプを特化することにします。

public class EmailAccountingTask : AccountingTask {...}
public class FaxAccountingTask : AccountingTask {...}
public class EmessagingAccountingTask : AccountingTask {...}

public class EmailContractTask : ContractTask {...}
public class FaxContractTask : ContractTask {...}
public class EmessagingContractTask : ContractTask {...}

public class EmailClaimTask : ClaimTask {...}
public class FaxClaimTask : ClaimTask {...}
public class EmessagingClaimTask : ClaimTask {...}

あなたは13のクラスに終わります。タスクタイプまたはソースタイプを追加することは困難になります。ブリッジパターンを使用すると、タスク(抽象化)をソース(実装上の問題)から分離することで、保守が容易になります。

// Source
public class Source {
   public string GetSender();
   public string GetMessage();
   public string GetContractReference();
   (...)
}

public class EmailSource : Source {...}
public class FaxSource : Source {...}
public class EmessagingSource : Source {...}

// Task
public class Task {
   public Task(Source source);
   (...)
}
public class AccountingTask : Task {...}
public class ContractTask : Task {...}
public class ClaimTask : Task {...}

タスクタイプまたはソースの追加がはるかに簡単になりました。

注:ほとんどの開発者は、この問題を処理するために13のクラス階層を事前に作成しません。ただし、実際には、ソースとタスクのタイプの数が事前にわからない場合があります。ソースが1つとタスクタイプが2つしかない場合は、タスクをソースから切り離さないでしょう。次に、新しいソースとタスクタイプが追加されると、全体的な複雑さが増します。ある時点で、リファクタリングを行い、ほとんどの場合、最終的にはブリッジのようなソリューションになります。


-5
Bridge design pattern we can easily understand helping of service and dao layer.

Dao layer -> create common interface for dao layer ->
public interface Dao<T>{
void save(T t);
}
public class AccountDao<Account> implement Dao<Account>{
public void save(Account){
}
}
public LoginDao<Login> implement Dao<Login>{
public void save(Login){
}
}
Service Layer ->
1) interface
public interface BasicService<T>{
    void save(T t);
}
concrete  implementation of service -
Account service -
public class AccountService<Account> implement BasicService<Account>{
 private Dao<Account> accountDao;
 public AccountService(AccountDao dao){
   this.accountDao=dao;
   }
public void save(Account){
   accountDao.save(Account);
 }
}
login service- 
public class LoginService<Login> implement BasicService<Login>{
 private Dao<Login> loginDao;
 public AccountService(LoginDao dao){
   this.loginDao=dao;
   }
public void save(Login){
   loginDao.save(login);
 }
}

public class BridgePattenDemo{
public static void main(String[] str){
BasicService<Account> aService=new AccountService(new AccountDao<Account>());
Account ac=new Account();
aService.save(ac);
}
}
}

5
これは複雑で形式が悪い回答だと思うので、反対票を投じました。
ジマノ2018年

1
コードのインデントと明確性に最小限の注意を払わずに、このサイトに回答を投稿するにはどうすればよいですか
Massimiliano Kraus
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.