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


154

有没有人在现实世界的应用程序中使用过桥接模式?如果是这样,您如何使用它?是我,还是只是在适配器模式中添加了少量依赖项注入?它真的值得拥有自己的模式吗?


请考虑接受此问题的其他答案。当前接受的答案不正确且无济于事。较新的答案要好得多。
jaco0646

GoF的书直接回答这个问题。
jaco0646

Answers:


76

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

在网桥模式中,适配器模式的某些方面是常见的观点。但是,引用本文

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


1
Bridge与模板或策略无关。桥梁是一种结构模式。模板和策略是行为模式。
jaco0646

249

费德里科约翰的答案结合在一起。

什么时候:

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

重构为:

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

6
你为什么要继承颜色?
vainolo

10
@vainolo,因为颜色是界面,蓝色,红色是具体颜色
Weltschmerz 2014年

3
这只是一个重构。桥接模式的意图:“将抽象与其实现分离,以便两者可以独立变化。” 抽象在哪里,实现在哪里?
clapas

1
Rectangle(Color)是否比BlueRectangle更抽象?
安东·夏斯塔尼

2
@clapas,抽象是属性的“ Shape.color”,因此类Red和Blue是实现,而Color接口是桥梁。
reco

230

Bridge模式是旧建议“优先于继承而不是继承”的应用。当您必须以彼此正交的方式将不同的时间子类化时,它将变得很方便。假设您必须实现彩色形状的层次结构。您不会将Rectangle和Circle子类化为Shape,然后将RedRectangle,BlueRectangle和GreenRectangle子化为Rectangle,对Circle进行子类化,是吗?您可能想说每个Shape 都有一个Color并实现颜色的层次结构,这就是Bridge Pattern。好吧,我不会实现“颜色层次结构”,但是您知道了...


1
有关此说明的图形说明,另请参见下面的Anton Shchastnyi图。
NomadeNumerique 2014年

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 2012年

1
使用数学概念来解释桥梁设计模式。很感兴趣。
黄健

1
这只是一个重构。桥接模式的意图:“将抽象与其实现分离,以便两者可以独立变化。” 抽象在哪里,实现在哪里?
clapas

约翰在博客中很好地表达了自己的看法文章中。发现它是高级概述的不错的阅读。
Vaibhav Bhalla

29

Adapter和Bridge当然是相关的,并且区别是微妙的。可能有些人以为自己正在使用其中一种模式,实际上正在使用另一种模式。

我看到的解释是,当您尝试统一一些已经存在的不兼容类的接口时,将使用Adapter 。适配器充当可以被认为是遗留的实现的转换器。

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

设备驱动程序是Bridge经常被引用的示例,但是如果您要为设备供应商定义接口规范,那我就说它是Bridge,但是如果您要使用现有的设备驱动程序并创建包装器类,则它是Adapter。提供统一的界面。

在代码方面,这两种模式非常相似。从业务角度来看,它们是不同的。

另请参阅http://c2.com/cgi/wiki?BridgePattern


嘿,比尔。我不明白为什么我们必须在设备驱动程序中使用桥接模式。我的意思是我们可以通过多态性轻松地将实现(读取,写入,查找等)委托给正确的类,对吗?还是与访客在一起?为什么必须是桥?提前致谢。
stdout

1
@zgulser,是的,您确实使用了多态性。桥接模式描述了一种使用子类将实现与抽象分离的方法。
Bill Karwin '16

您是说让Shape实现脱离耦合(即Rectangle)颜色抽象对吗?我相信您是在说有多种方法可以做到,而Bridge只是其中一种。
stdout

是的,子类化还有其他用途。这种使用子类的特殊方式使其成为Bridge模式。
Bill Karwin '16

我的意思是从抽象的Shape接口到具体的Rectangle实现解耦。因此,即使具体对象确实是Shape的某些子类,您也可以编写需要“ Shape”类型的对象的代码。
Bill Karwin '16

27

以我的经验,Bridge是一个经常重复出现的模式,因为只要域中有两个正交维,Bridge就是解决方案。例如形状和绘制方法,行为和平台,文件格式和序列化程序等。

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


22

的意图适配器是不同的,我们需要分别两个模式。

桥接模式:

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

在以下情况下使用桥接模式:

  1. 您想要实现的运行时绑定,
  2. 耦合的界面和众多的实现导致您的类激增,
  3. 您想在多个对象之间共享实现,
  4. 您需要映射正交的类层次结构。

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

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

适配器模式

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

主要区别:

  1. 适配器使事情在设计之后就可以正常工作。让他们先于工作。
  2. Bridge是预先设计的,以使抽象和实现独立变化适配器进行了改造,以使不相关的类一起工作。
  3. 意图:适配器允许两个不相关的接口一起工作。Bridge允许抽象和实现独立变化。

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

桥接模式和适配器模式之间的区别

有用的文章:

制作桥梁模式文章

来源制作适配器模式文章

journaldev桥接模式文章

编辑:

桥接模式的真实示例(根据meta.stackoverflow.com的建议,由于文档即将落山,因此本文中并入了文档站点示例)

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

维基百科的桥接模式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();
    }
}

输出:

Car handles Manual gear
Car handles Auto gear
Truck handles Manual gear
Truck handles Auto gear

说明:

  1. Vehicle 是一个抽象。
  2. CarTruck是的两个具体实现Vehicle
  3. Vehicle定义一个抽象方法:addGear()
  4. Gear 是实现者接口
  5. ManualGearAutoGear是两种实现 Gear
  6. Vehicle包含implementor接口,而不是实现接口。Compositon实现者接口的概念是这种模式的关键:它允许抽象和实现独立变化。
  7. CarTruck为抽象定义实现(重新定义的抽象)addGear()::它包含 Gear- ManualAuto

桥接模式的用例

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

“适配器使事情在设计之初就起作用了; Bridge使事情在其先于事物起作用。” 您可能需要查看可插拔适配器。这是GoF在其“设计模式”书的“适配器”部分中描述的Adapter的变体。目的是为尚不存在的类创建接口。可插拔适配器不是网桥,所以我觉得第一点是无效的。
c1moore

尽管手动挡和
自动挡

9

我在工作中使用了桥接模式。我使用C ++进行编程,通常将其称为PIMPL习惯用法(实现的指针)。看起来像这样:

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

class Aimpl
{
public:
  void foo();
  void bar();
};  

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

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

另一个好处是,您可以Aimpl在一个单独的头文件中定义,而头文件无需由的用户定义A。您所要做的就是使用Aimplbefore的前向声明A,并将引用的所有成员函数的定义移到pImpl.cpp文件中。这使您能够保留Aimpl标头的私有性,并减少编译时间。


2
如果使用此模式,则AImpl甚至不需要标题。我只是将其内联到A类的实现文件中
1800信息

您的实现者是私有的。我对此有一个新问题,请参见stackoverflow.com/questions/17680762/…–
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类层次结构来处理此问题。但是,在现实生活中,您可能不预先知道源和任务类型的数量。如果您只有一个来源和两种任务类型,则可能不会将Task与Source分离。然后,随着添加新的来源和任务类型,总体复杂性也随之增加。在某个时候,您将进行重构,并且通常会得到类似桥梁的解决方案。


-4
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
我之所以投票,是因为我觉得这是一个令人费解的,格式不正确的答案。
Zimano

1
完全同意,您如何在不最小关注代码缩进和清晰性的情况下,在此网站上发布答案
Massimiliano Kraus
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.