你什么时候使用桥模式?它与适配器模式有何不同?

Cha*_*ham 150 design-patterns bridge adapter

有没有人在现实世界的应用程序中使用Bridge Pattern?如果是这样,你是如何使用它的?是我,还是仅仅是适配器模式,在混合中引入了一点依赖注入?它真的值得拥有自己的模式吗?

Ant*_*nyi 242

费德里科约翰的答案相结合.

什么时候:

                   ----Shape---
                  /            \
         Rectangle              Circle
        /         \            /      \
BlueRectangle  RedRectangle BlueCircle RedCircle
Run Code Online (Sandbox Code Playgroud)

重构为:

          ----Shape---                        Color
         /            \                       /   \
Rectangle(Color)   Circle(Color)           Blue   Red
Run Code Online (Sandbox Code Playgroud)

  • @vainolo因为Color是一个界面而Blue,Red是具体的颜色 (10认同)
  • 你为什么要继承颜色? (6认同)
  • 这只是一个重构.桥模式意图:"将抽象与其实现分离,以便两者可以独立变化." 抽象在哪里以及这里的实现在哪里? (3认同)
  • @clapas,Abstraction是属性的"Shape.color",因此类Red和类Blue是实现,Color接口是桥接. (2认同)
  • 我读到了数十个不恰当的例子,它们只是滥用了继承,即自然感觉像“有一个”的东西被呈现为“是一个”。这是我找到的最好的答案。太棒了!简短、信息丰富、概述。 (2认同)

Fed*_*oni 227

Bridge模式是旧建议的应用,"更喜欢组合而不是继承".当你必须以彼此正交的方式子类化不同的时间时,它变得很方便.假设您必须实现彩色形状的层次结构.你不会使用Rectangle和Circle子类化Shape,然后使用RedRectangle,BlueRectangle和GreenRectangle将Rectangle子类化为Circle,对于Circle也是如此,是吗?您更愿意说每个Shape 都有一个Color并实现颜色层次结构,这就是Bridge Pattern.好吧,我不会实现"颜色层次",但你明白了......

  • 我认为颜色不是实现层次结构的一个很好的例子,而是令人困惑。GoF在“设计模式”中有一个很好的例子,其中的实现依赖于平台:IBM的PM,UNIX的X等。 (2认同)

Joh*_*mez 211

什么时候:

        A
     /     \
    Aa      Ab
   / \     /  \
 Aa1 Aa2  Ab1 Ab2
Run Code Online (Sandbox Code Playgroud)

重构为:

     A         N
  /     \     / \
Aa(N) Ab(N)  1   2
Run Code Online (Sandbox Code Playgroud)

  • 我认为这是非常实用的模式方法:1)描述次优的直接设计2)重构设计/代码以更好地考虑因素 (3认同)
  • 使用数学概念来解释 Bridge 设计模式。很感兴趣。 (2认同)

she*_*hek 76

Bridge模式的经典示例用于UI环境中的形状定义(请参阅Bridge模式Wikipedia条目).桥接模式是一个复合的的模板策略模式.

桥模式中适配器模式的某些方面是常见的视图.但是,引用本文:

乍一看,Bridge模式看起来很像Adapter模式,因为一个类用于将一种接口转换为另一种接口.但是,适配器模式的目的是使一个或多个类的接口看起来与特定类的接口相同.Bridge模式旨在将类的接口与其实现分开,以便您可以在不更改客户端代码的情况下更改或替换实现.

  • Bridge 与模板或策略无关。桥是一种结构模式。模板和策略是行为模式。 (4认同)

Bil*_*win 29

适配器和桥肯定是相关的,区别是微妙的.有些人认为他们使用其中一种模式实际上是在使用其他模式.

我看到的解释是,当您尝试统一已存在的某些不兼容类的接口时,将使用适配器.适配器作为一种可被视为遗留的实现的转换器.

而Bridge模式用于更可能是绿地的代码.您正在设计Bridge以为需要变化的实现提供抽象接口,但您还要定义这些实现类的接口.

设备驱动程序是一个经常被引用的Bridge示例,但是如果您为设备供应商定义接口规范,我会说它是一个桥接器,但如果您正在使用现有的设备驱动程序并创建一个包装类,那么它就是一个适配器.提供统一的界面.

所以代码方面,这两种模式非常相似.在商业方面,他们是不同的.

另见http://c2.com/cgi/wiki?BridgePattern


thS*_*oft 26

根据我的经验,Bridge是一种经常反复出现的模式,因为只要域中有两个正交维度,它就是解决方案.例如形状和绘图方法,行为和平台,文件格式和序列化器等.

建议:始终从概念角度考虑设计模式,而不是从实施角度考虑.从正确的角度来看,Bridge不能与Adapter混淆,因为它们解决了不同的问题,并且组合优于继承而不是因为它本身,而是因为它允许分别处理正交问题.


Rav*_*abu 20

BridgeAdapter的意图是不同的,我们需要分别使用这两种模式.

桥梁图案:

  1. 这是一种结构模式
  2. 抽象和实现不受编译时的约束
  3. 抽象和实施 - 两者都可以在客户端没有影响的情况下变化
  4. 使用组合而不是继承.

在以下情况下使用Bridge模式:

  1. 你想要实现的运行时绑定,
  2. 你有很多类来自耦合接口和众多实现,
  3. 您想要在多个对象之间共享实现,
  4. 您需要映射正交类层次结构.

@ John Sonmez回答清楚地表明了桥模式在减少类层次结构方面的有效性.

您可以参考以下文档链接,通过代码示例更好地了解桥接模式

适配器模式:

  1. 允许两个不相关的接口通过不同的对象一起工作,可能扮演相同的角色.
  2. 它修改了原始界面.

主要差异:

  1. 适配器在设计完成后使其工作; Bridge让它们在它们之前工作.
  2. Bridge是预先设计的,让抽象和实现独立变化.改造适配器以使不相关的类一起工作.
  3. 意图:适配器允许两个不相关的接口一起工作.Bridge允许抽象和实现独立变化.

与UML图和工作代码相关的SE问题:

桥模式和适配器模式之间的差异

有用的文章:

源头桥模式文章

源代码适配器模式文章

journaldev桥模式文章

编辑:

Bridge Pattern真实世界的例子(根据meta.stackoverflow.com建议,在本文中包含文档站点示例,因为文档将进行sun-set)

桥接模式将抽象与实现分离,以便两者可以独立变化.它是通过组合而不是继承来实现的.

来自维基百科的桥模式UML:

来自维基百科的桥模式UML

这种模式中有四个组件.

Abstraction:它定义了一个接口

RefinedAbstraction:它实现了抽象:

Implementor:它定义了一个实现接口

ConcreteImplementor:它实现了Implementor接口.

The crux of Bridge pattern :使用组合(并且没有继承)的两个正交类层次结构.抽象层次结构和实现层次结构可以独立变化.实现永远不会引用抽象.抽象包含实现接口作为成员(通过组合).此组合减少了一个级别的继承层次结构.

真实用例:

使不同的车辆具有手动和自动齿轮系统的两种版本.

示例代码:

/* 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

Car handles Manual gear
Car handles Auto gear
Truck handles Manual gear
Truck handles Auto gear
Run Code Online (Sandbox Code Playgroud)

说明:

  1. Vehicle 是一种抽象.
  2. Car并且Truck是两个具体的实现Vehicle.
  3. Vehicle定义一个抽象方法:addGear().
  4. Gear 是实现者接口
  5. ManualGear并且AutoGear是两个实现 Gear
  6. Vehicle包含implementor接口而不是实现接口.Compositon实现者接口是这种模式的关键:它允许抽象和实现独立变化.
  7. CarTruck为抽象定义实现(重新定义的抽象)addGear():它包含 Gear- 或者Manual或者Auto

Bridge模式的用例:

  1. 抽象实现可以彼此独立地更改,并且它们在编译时不受约束
  2. 映射正交层次结构 - 一个用于抽象,一个用于实现.


Dim*_*ima 10

我在工作中使用了桥模式.我在C++中编程,它通常被称为PIMPL习语(指向实现的指针).它看起来像这样:

class A
{
public: 
  void foo()
  {
    pImpl->foo();
  }
private:
  Aimpl *pImpl;
};

class Aimpl
{
public:
  void foo();
  void bar();
};  
Run Code Online (Sandbox Code Playgroud)

在此示例中class A包含接口,并class Aimpl包含实现.

此模式的一个用途是仅公开实现类的一些公共成员,而不公开其他公共成员.在示例中,只能Aimpl::foo()通过公共接口调用A,但不能Aimpl::bar()

另一个优点是您可以Aimpl在单独的头文件中定义,不需要用户包含A.您所要做的就是使用Aimplbefore 的forward声明A,并将引用的所有成员函数的定义移动pImpl到.cpp文件中.这使您能够保持Aimpl标头的私密性,并减少编译时间.

  • 如果使用此模式,则AImpl甚至不需要标头.我只是将它内联在A类的实现文件中 (2认同)

Not*_*ica 6

在代码中放置形状示例:

#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;
}
Run Code Online (Sandbox Code Playgroud)

输出是:

Drawn a Square of Red Color
Drawn a Circle of Blue Color
Run Code Online (Sandbox Code Playgroud)

请注意,可以轻松地将新颜色和形状添加到系统中,而不会因排列而导致子类爆炸.


Syl*_*gue 6

您在一家保险公司工作,开发一个工作流程应用程序来管理不同类型的任务:会计、合同、索赔。这就是抽象。在实施方面,您必须能够从不同来源创建任务:电子邮件、传真、电子消息。

您可以从这些类开始您的设计:

public class Task {...}
public class AccountingTask : Task {...}
public class ContractTask : Task {...}
public class ClaimTask : Task {...}
Run Code Online (Sandbox Code Playgroud)

现在,由于必须以特定方式处理每个源,因此您决定专门化每个任务类型:

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 {...}
Run Code Online (Sandbox Code Playgroud)

你最终有 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 {...}
Run Code Online (Sandbox Code Playgroud)

添加任务类型或来源现在变得更加容易。

注意:大多数开发人员不会预先创建 13 个类层次结构来处理此问题。然而,在现实生活中,您可能事先不知道源和任务类型的数量;如果您只有一种源和两种任务类型,您可能不会将任务与源分离。然后,随着新来源和任务类型的添加,整体复杂性会增加。在某些时候,您将进行重构,并且最常见的是最终得到一个类似桥的解决方案。