什么时候使用桥接模式?它与适配器模式有何不同?
Posted
技术标签:
【中文标题】什么时候使用桥接模式?它与适配器模式有何不同?【英文标题】:When to use the Bridge pattern and how is it different from the Adapter pattern? 【发布时间】:2010-09-24 02:23:41 【问题描述】:有没有人在现实世界的应用程序中使用过Bridge Pattern?如果是这样,你是如何使用它的?是我,还是只是适配器模式加上一点依赖注入?它真的值得拥有自己的模式吗?
【问题讨论】:
GoF book 直接回答了这个问题。 【参考方案1】:Federico's 和 John's 答案的组合。
时间:
----Shape---
/ \
Rectangle Circle
/ \ / \
BlueRectangle RedRectangle BlueCircle RedCircle
重构为:
----Shape--- Color
/ \ / \
Rectangle(Color) Circle(Color) Blue Red
【讨论】:
为什么要继承颜色? @vainolo 因为颜色是界面,蓝色、红色是具体颜色 这只是一个重构。桥接模式意图:“将抽象与其实现分离,以便两者可以独立变化。”抽象在哪里,实现在哪里? 不是 Rectangle(Color) 比 BlueRectangle 更抽象吗? @clapas , Abstraction 是属性的“Shape.color”,因此 Red 类和 Blue 类是实现,Color 接口是桥梁。【参考方案2】:桥接模式是旧建议的应用,“更喜欢组合而不是继承”。 当您必须以相互正交的方式对不同时间进行子类化时,它会变得很方便。假设您必须实现彩色形状的层次结构。你不会用 Rectangle 和 Circle 子类 Shape,然后用 RedRectangle、BlueRectangle 和 GreenRectangle 子类 Rectangle,Circle 也是如此,对吗?您更愿意说每个形状有一个颜色并实现颜色的层次结构,这就是桥接模式。好吧,我不会实现“颜色层次结构”,但你明白了......
【讨论】:
另请参阅下面的 Anton Shchastnyi 图,了解此解释的图解说明。 我不认为颜色是实现层次结构的好例子,它相当令人困惑。 GoF 的“设计模式”中有一个很好的桥接模式示例,其实现取决于平台:IBM 的 PM、UNIX 的 X 等。【参考方案3】:时间:
A
/ \
Aa Ab
/ \ / \
Aa1 Aa2 Ab1 Ab2
重构为:
A N
/ \ / \
Aa(N) Ab(N) 1 2
【讨论】:
我认为这是一种非常实用的模式方法:1)描述次优的直接设计 2)重构设计/代码以更好地分解 使用数学概念来解释桥接设计模式。很感兴趣。 这只是一个重构。桥接模式意图:“将抽象与其实现分离,以便两者可以独立变化。”抽象在哪里,实现在哪里? John 在blog 帖子中很好地说明了这一点。发现这是一本很好的高级概述读物。 很好,你有其他模式的类似图表吗?【参考方案4】:在 UI 环境中定义形状时使用了桥模式的经典示例(请参阅Bridge pattern Wikipedia entry)。 Bridge 模式是Template 和Strategy 模式中的composite。
这是桥模式中适配器模式某些方面的常见观点。但是,引用this article:
乍一看,桥接模式看起来很像适配器模式,因为一个类用于将一种接口转换为另一种接口。但是,适配器模式的目的是使一个或多个类的接口看起来与特定类的接口相同。桥接模式旨在将类的接口与其实现分开,这样您就可以在不更改客户端代码的情况下更改或替换实现。
【讨论】:
Bridge 与模板或策略无关。桥梁是一种结构模式。模板和策略是行为模式。【参考方案5】:根据我的经验,Bridge 是一种经常重复出现的模式,因为只要域中有两个正交维度,它就是解决方案。例如。形状和绘制方法、行为和平台、文件格式和序列化程序等等。
还有一个建议:始终从概念的角度考虑设计模式,而不是从实现的角度。从正确的角度来看,Bridge 不能与 Adapter 混淆,因为它们解决了不同的问题,组合优于继承不是因为它本身,而是因为它允许单独处理正交关注点。
【讨论】:
【参考方案6】:Adapter 和 Bridge 肯定是相关的,而且区别很微妙。很可能有些人认为他们正在使用其中一种模式,实际上他们正在使用另一种模式。
我看到的解释是当你试图统一一些已经存在的不兼容类的接口时使用了Adapter。适配器充当一种翻译器,可被视为legacy的实现。
而桥模式用于更有可能是未开发的代码。您正在设计 Bridge 为需要变化的实现提供抽象接口,但您还定义了这些实现类的接口。
设备驱动程序是 Bridge 的一个经常被引用的示例,但如果您为设备供应商定义接口规范,我会说它是一个 Bridge,但如果您使用现有的设备驱动程序并制作一个包装器,它就是一个适配器-class 提供统一的接口。
所以在代码方面,这两种模式非常相似。在业务方面,它们是不同的。
另见http://c2.com/cgi/wiki?BridgePattern
【讨论】:
嘿比尔。我不明白为什么我们必须在设备驱动程序中使用桥接模式。我的意思是我们可以通过多态性轻松地将实现(读、写、查找等)委托给正确的类,对吧?或者也许有访客?为什么一定要桥牌?提前致谢。 @zgulser,是的,你确实使用了多态性。桥接模式描述了一种使用子类将实现与抽象分离的用法。 您的意思是将 Shape 实现(即矩形)从我们的一天颜色抽象中解耦,对吗?而且我相信您是说有多种方法可以做到这一点,而 Bridge 只是其中之一。 是的,子类化还有其他用途。这种使用子类的特殊方式使其成为桥模式。 我的意思是解耦是从抽象的 Shape 接口到具体的 Rectangle 实现。因此,您可以编写需要“Shape”类型对象的代码,即使具体对象实际上是 Shape 的某个子类。【参考方案7】:Bridge 和 Adapter 的意图不同,我们分别需要这两种模式。
桥接模式:
-
这是一种结构模式
抽象和实现在编译时不受约束
抽象和实现 - 两者都可以变化而不会对客户端产生影响
使用组合而不是继承。
在以下情况下使用桥接模式:
-
您想要实现的运行时绑定,
由于耦合接口和众多实现,您拥有大量的类,
您希望在多个对象之间共享一个实现,
您需要映射正交类层次结构。
@ John Sonmez 的回答清楚地表明了桥接模式在减少类层次结构方面的有效性。
您可以参考以下文档链接,通过代码示例更好地了解桥接模式
适配器模式:
-
它允许两个不相关的接口通过不同的对象一起工作,可能扮演相同的角色。
它修改了原来的界面。
主要区别:
-
Adapter 让事情在设计之后就可以工作; Bridge 让他们先于工作。
Bridge 是预先设计的,让抽象和实现独立变化。 Adapter 进行了改造,使不相关的类可以协同工作。
意图:适配器允许两个不相关的接口一起工作。 Bridge 允许抽象和实现独立变化。
与 UML 图和工作代码相关的 SE 问题:
Difference between Bridge pattern and Adapter pattern
有用的文章:
sourcemaking bridge模式文章
sourcemaking adapter模式文章
journaldev bridge模式文章
编辑:
Bridge Pattern 真实世界示例(根据 meta.***.com 的建议,在这篇文章中包含文档站点示例,因为文档即将结束)
桥接模式将抽象与实现分离,因此两者都可以独立变化。它是通过组合而不是继承实现的。
来自***的桥接模式 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
解释:
Vehicle
是一个抽象。
Car
和 Truck
是 Vehicle
的两个具体实现。
Vehicle
定义了一个抽象方法:addGear()
。
Gear
是实现接口
ManualGear
和 AutoGear
是 Gear
的两个实现
Vehicle
包含 implementor
接口而不是实现接口。实现者接口的Compositon
是这种模式的关键:它允许抽象和实现独立变化。
Car
和 Truck
为抽象定义实现(重新定义抽象):addGear()
:它包含 Gear
- Manual
或 Auto
桥接模式用例:
-
抽象和实现可以相互独立变化,并且在编译时不受约束
映射正交层次结构 - 一个用于抽象,一个用于实现。
【讨论】:
"适配器在设计完成后使它们工作;Bridge 使它们在设计之前工作。"您可能需要查看 Pluggable Adapter。它是 GoF 在其设计模式书的“适配器”部分中描述的适配器的变体。目的是为尚不存在的类创建一个接口。可插拔适配器不是桥接器,所以我觉得第一点不成立。 虽然手动和自动齿轮可能需要卡车和汽车不同的实施方式【参考方案8】:我在工作中使用了桥接模式。我用 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
的用户不需要包含该文件。您所要做的就是在定义A
之前使用Aimpl
的前向声明,并将所有引用pImpl
的成员函数的定义移动到.cpp 文件中。这使您能够保持 Aimpl
标头私有,并减少编译时间。
【讨论】:
如果你使用这种模式,那么 AImpl 甚至不需要标题。我只是把它内联在A类的实现文件中 你的实现者是私有的。我对此有一个新问题,请参阅***.com/questions/17680762/…【参考方案9】:将形状示例放入代码中:
#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
请注意,可以轻松地将新颜色和形状添加到系统中,而不会因排列而导致子类爆炸。
【讨论】:
【参考方案10】:您在一家保险公司工作,您在该公司开发一个工作流应用程序来管理不同类型的任务:会计、合同、索赔。这就是抽象。在实施方面,您必须能够从不同来源创建任务:电子邮件、传真、电子邮件。
你从这些类开始你的设计:
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 个类层次结构来处理此问题。但是,在现实生活中,您可能事先不知道源和任务类型的数量;如果您只有一个源和两种任务类型,您可能不会将任务与源分离。然后,随着新源和任务类型的添加,整体复杂性会增加。在某些时候,您将进行重构,并且通常会得到一个类似桥的解决方案。
【讨论】:
【参考方案11】:对我来说,我认为它是一种可以交换接口的机制。在现实世界中,您可能有一个可以使用多个接口的类,Bridge 允许您交换。
【讨论】:
【参考方案12】:如果你得到相同的旧形状和颜色示例的板,我会给你一个新的桥模式示例。
假设您有不同的付款方式,例如信用卡付款和网上银行。还有花旗银行和汇丰银行等不同的支付网关。
然后您只需将支付网关成员添加到支付模式即可。并在运行时将此信息传递给支付模式对象。然后付款。
例如,它将在 CITI 银行支付网关上进行卡支付。
【讨论】:
【参考方案13】: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);
【讨论】:
我投了反对票,因为我觉得这是一个令人费解、格式错误的答案。 完全同意,你怎么能在这个网站上发布答案而不注意代码缩进和清晰度以上是关于什么时候使用桥接模式?它与适配器模式有何不同?的主要内容,如果未能解决你的问题,请参考以下文章