何时使用 Bridge 模式,它与 Adapter 模式有何不同?

有人曾经在现实世界的应用程序中使用过 舰桥模式吗?如果是这样,你是如何使用它的?只有我这么觉得还是只有 abc1加了点依赖注入?它真的值得拥有自己的模式吗?

92765 次浏览

Bridge 模式的一个经典示例用于 UI 环境中形状的定义(参见 桥模式,维基百科条目)。桥模式是 模板策略模式的 合成的

这是 Bridge 模式中 Adapter 模式的某些方面的常见观点。然而,引用 这篇文章的话:

乍一看,Bridge 模式与 Adapter 模式非常相似,因为类用于将一种接口转换为另一种接口。然而,Adapter 模式的目的是使一个或多个类的接口看起来与特定类的接口相同。Bridge 模式的设计目的是将类的接口与其实现分离开来,这样您就可以在不更改客户端代码的情况下更改或替换实现。

Bridge 模式是旧建议的一个应用,即“更喜欢组合而不是继承”。 当您必须以彼此正交的方式子类化不同的时间时,它变得很方便。假设您必须实现彩色形状的层次结构。你不会子类形状与矩形和圆,然后子类矩形与红矩形,蓝矩形和绿矩形和相同的圆,你会吗?您更愿意说,每个形状 已经一个颜色和实现的颜色层次结构,这是桥模式。好吧,我不会实现一个“颜色层次”,但你得到的想法..。

适配器和桥当然是相关的,区别是微妙的。很可能有些人认为他们正在使用其中一种模式,实际上却在使用另一种模式。

我看到的解释是,当您试图统一 已经存在了中某些不兼容类的接口时,会使用 Adapter。Adapter 的作用是将实现转换为可以被认为是 遗产的实现。

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

设备驱动程序是一个经常被引用的 Bridge 的例子,但是如果你正在为设备供应商定义接口规范,我会说它是一个 Bridge,但是如果你正在使用现有的设备驱动程序并制作一个包装类来提供一个统一的接口,那么它就是一个 Adapter。

因此,在代码方面,这两种模式非常相似。在业务方面,它们是不同的。

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

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

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


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

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

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

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

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

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

时间:

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

重构为:

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

有一个 费德里科的约翰的答案的组合。

时间:

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

重构为:

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

对我来说,我认为它是一种可以交换接口的机制。在现实世界中,您可能有一个可以使用多个接口的类,Bridge 允许您进行交换。

在代码中放入形状示例:

#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

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

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

桥型:

  1. 这是一种结构模式
  2. 抽象和实现在编译时不受限制
  3. 抽象和实现——两者都可以在不影响客户端的情况下发生变化
  4. 使用组合而不是继承。

在下列情况下使用“桥”模式:

  1. 您想要实现的运行时绑定,
  2. 由耦合接口和大量实现产生的类大量增加,
  3. 要在多个对象之间共享一个实现,
  4. 您需要映射正交的类层次结构。

@ John Sonmez 的回答清楚地表明了桥模式在降低阶级等级方面的有效性。

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

适配器模式 :

  1. 它通过 允许两个不相关的接口协同工作不同的对象,可能发挥相同的作用。
  2. 它修改了原来的界面。

主要区别:

  1. Adapter 使事物在设计之后工作; 舰桥使事物在设计之前工作。
  2. 是预先设计的,让 抽象和实现各不相同适配器是改造,使不相关的类一起工作。
  3. 意图: 适配器允许两个不相关的接口协同工作。 舰桥允许抽象和实现独立变化。

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

桥接模式与适配器模式的区别

有用文章:

源头造桥 模式文章

源代码生成适配器 模式文章

Journaldev bridge pattern article

编辑:

桥梁模式真实世界的例子(根据 meta.stackoverflow.com 的建议,在本文中加入了文档网站的例子,因为文档将在日落时分发布)

桥模式将抽象与实现解耦,这样两者都可以独立变化。它是通过组合而不是继承来实现的。

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

Bridge pattern UML from Wikipedia

这个模式有四个组成部分。

Abstraction: 它定义了一个接口

RefinedAbstraction: 它实现抽象:

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

ConcreteImplementor: 它实现了实现者接口。

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. CarTruckVehicle的两个具体实现。
  3. Vehicle定义了一个抽象方法: addGear()
  4. Gear是实现者接口
  5. ManualGearAutoGearGear的两个实现
  6. Vehicle包含 implementor接口而不是实现接口。实现者接口的 Compositon是这种模式的关键: 它允许抽象和实现独立变化。
  7. CarTruck定义抽象的实现(重新定义的抽象) : addGear(): 它包含 Gear-ManualAuto

Bridge 模式的用例 :

  1. 抽象 实施可以相互独立地更改,并且在编译时不绑定它们
  2. 映射正交层次结构——一个用于 抽象,一个用于 实施
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);
}
}
}

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

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

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 分离。然后,随着新源和任务类型的添加,整体复杂性会增加。在某些时候,您将重构,并且最常见的是,最终得到一个类似于桥的解决方案。

我将给你一个新的桥梁模式的例子,如果你得到相同的旧的形状和颜色的例子董事会。

假设你有不同的支付方式,比如信用卡支付和网上银行。此外,还有不同的支付门户,如花旗银行(CITI)和汇丰银行(HSBC)。

然后您可以只添加支付网关成员的支付模式。并在运行时将此信息传递给支付模式对象。然后付款。

因此,例如,它将使信用卡支付在 CITI 银行支付网关。

Adapter 和 Bridge 设计模式之间的关键区别在于它们的意图。摘自 设计模式,第4章,“桥梁”部分,“相关模式”(Gamma 等人,1994年) :

Adapter (139)模式旨在使不相关的类一起工作。它通常在系统设计完成后应用于系统。另一方面,桥在设计中是预先使用的,以便让抽象和实现独立变化。

  1. 单词“独立”意味着桥梁设计模式适用于这种情况,因为形状和颜色是独立的:
             ------Shape-----                           Shape       Colour
/                \              Bridge       / \         / \
Circle                Square         ----->  Circle Square  Red Blue
/ \                   / \
RedCircle BlueCircle  RedSquare BlueSquare
  1. 但它不适用于这种情况,因为颜色取决于形状:
             ------Shape-----
/                \
Circle                Square
/ \                   / \
RedCircle BlueCircle  RedSquare GreenSquare
  1. 在这种情况下它是无用的,因为只有一种形状和一种颜色:
Shape
|
Circle
|
RedCircle

情况表1:

| Shape  | Colour |          | Shape  |  | Colour |
| ------ | ------ |          | ------ |  | ------ |
| circle | red    |  Bridge  | circle |  | red    |
| circle | blue   |  ----->  | square |  | blue   |
| square | red    |
| square | blue   |

情况2表:

| Shape  | Colour |
| ------ | ------ |
| circle | red    |
| circle | blue   |
| square | red    |
| square | green  |

情况3表:

| Shape  | Colour |
| ------ | ------ |
| circle | red    |

因此,面向对象程序设计中的桥设计模式等同于关系数据库中的投影-连接规范形式,即 PJ/NF (费金1979年)。

在情境1中,关系模式 R(形状,颜色)具有多值依赖关系 something {形状}(独立的形状)和 something {颜色}(独立的颜色) ,这些关系不是由关键依赖关系{ KEY ({形状,颜色})}所暗示的,所以它不在 PJ/NF 中。它的投影在 PJ/NF 中,因为 R1(塑形)具有平凡的函数依赖关系{形}→{形} ,这是由一组关键依赖关系所暗示的{ KEY ({塑形})}和 R2(颜色)具有平凡的函数依赖关系{颜色}→{颜色} ,这是由一组关键依赖关系所暗示的{ KEY ({颜色})}。

在情况2中,关系模式 R(形状,颜色)有一个微不足道的多值依赖{形状}{颜色} ,这是一组关键依赖关系{ KEY ({形状,颜色})}所暗示的,所以它已经在 PJ/NF 中了。

在第三种情况下,关系模式 R(塑形,颜色)具有关键依赖关系集合{ KEY ({塑形,颜色}) ,KEY (something)}所隐含的函数依赖关系 something →{塑形}(单形)和 something →{颜色}(单色) ,所以它已经存在于 PJ/NF 中。