桥接模式和适配器模式有什么区别?
11 回答
“适配器在设计完成后让它们工作;Bridge 让它们在设计之前工作。[GoF,p219]”
实际上,当您拥有现有代码(无论是第三方代码还是内部代码)但无法控制或无法更改以完全满足您需要的接口时,适配器模式非常有用。例如,我们有一个 SuperWeaponsArray,它可以控制一组精细的末日设备。
public class SuperWeaponsArray {
/*...*/
public void destroyWorld() {
for (Weapon w : armedWeapons) {
w.fire();
}
}
}
伟大的。除了我们意识到我们的武器库中有一个核装置,它大大早于武器界面的转换。但我们真的很希望它在这里工作......所以我们该怎么办......把它楔入!
NukeWeaponsAdaptor - 基于我们的 Nuke 类,但导出 Weapon 接口。亲爱的,现在我们肯定可以毁灭世界了。这似乎有点杂乱无章,但它使事情顺利进行。
桥接模式是你预先实现的——如果你知道你有两个正交的层次结构,它提供了一种解耦接口和实现的方法,这样你就不会得到太多的类。假设您有:
MemoryMappedFile 和 DirectReadFile 类型的文件对象。假设您希望能够从各种来源(可能是 Linux 与 Windows 实现等)读取文件。Bridge 可帮助您避免清盘:
MemoryMappedWindowsFile MemoryMappedLinuxFile DirectReadWindowsFile DirectReadLinuxFile
http://en.wikipedia.org/wiki/Adapter_pattern
适配器模式更多的是让您现有的代码与更新的系统或接口一起工作。
如果您想将一组公司标准的 Web 服务 API 提供给另一个应用程序的现有可扩展性接口,您可能会考虑编写一组适配器来执行此操作。请注意,有一个灰色区域,这更多是关于您如何在技术上定义模式,因为其他模式(如外观)是相似的。
http://en.wikipedia.org/wiki/Bridge_pattern
桥接模式将允许您可能拥有算法或系统的替代实现。
虽然不是典型的 Bridge 模式示例,但想象一下,如果您有几个数据存储实现:一个在空间上是高效的,另一个在原始性能方面是高效的......并且您有一个在您的应用程序或框架中提供的商业案例.
就您的问题“我可以在哪里使用哪种模式”而言,答案是,只要它对您的项目有意义!也许考虑提供一个澄清编辑来指导您认为需要使用其中一个或另一个的讨论。
适配器:
- 它是一种结构模式
- 使用两个不兼容的接口很有用
UML 图:来自dofactory文章:
Target :定义客户端使用的特定于域的接口。
Adapter:将接口 Adaptee 适配到 Target 接口。
Adaptee:定义一个需要适配的现有接口。
Client :与符合 Target 接口的对象协作。
例子:
Square 和 Rectangle 是两种不同的形状,获取它们中的每一个的 area() 需要不同的方法。但是 Square 仍然在 Rectangle 接口上工作,并转换了一些属性。
public class AdapterDemo{
public static void main(String args[]){
SquareArea s = new SquareArea(4);
System.out.println("Square area :"+s.getArea());
}
}
class RectangleArea {
public int getArea(int length, int width){
return length * width;
}
}
class SquareArea extends RectangleArea {
int length;
public SquareArea(int length){
this.length = length;
}
public int getArea(){
return getArea(length,length);
}
}
桥:
- 它是结构模式
- 它将抽象与其实现分离,两者都可以独立变化
- 这是可能的,因为已使用组合代替继承
编辑:(根据@quasoft 的建议)
在这个模式中有四个组件。
抽象:它定义了一个接口
RefinedAbstraction:它实现了抽象:
实现者:它定义了一个实现接口
ConcreteImplementor:它实现了 Implementor 接口。
代码片段:
Gear gear = new ManualGear();
Vehicle vehicle = new Car(gear);
vehicle.addGear();
gear = new AutoGear();
vehicle = new Car(gear);
vehicle.addGear();
相关帖子:
主要区别:来自来源制作文章
- 适配器在设计完成后就可以工作;Bridge 让它们先于它们工作。
- Bridge 是预先设计的,让抽象和实现独立变化。改装适配器以使不相关的类一起工作。
这个帖子已经存在了很长一段时间。但是,重要的是要了解外观与适配器有些相似,但并不完全相同。适配器将现有类“调整”到通常不兼容的客户端类。假设您有一个旧的工作流系统,您的应用程序将其用作客户端。您的公司可能会用新的“不兼容”系统(在接口方面)替换工作流系统。在大多数情况下,您可以使用适配器模式并编写实际调用新工作流引擎接口的代码。桥通常以不同的方式使用。如果您确实有一个需要使用不同文件系统(即本地磁盘、NFS 等)的系统,您可以使用桥接模式并创建一个抽象层来处理所有文件系统。这基本上是桥接模式的一个简单用例。Facade 和适配器确实共享一些属性,但是外观通常用于简化现有的接口/类。在 EJB 的早期,没有本地调用 EJB。开发人员总是获取存根,将其缩小并称之为“伪远程”。这通常会导致性能问题(尤其是当真正通过网络调用时)。有经验的开发人员会使用外观模式为客户端提供一个非常粗粒度的接口。然后这个门面会依次对不同的更细粒度的方法进行多次调用。总而言之,这大大减少了所需的方法调用次数并提高了性能。
在最佳答案中,@James 引用了 GoF 第 219 页的一句话。我认为值得在这里复制完整的解释。
适配器与桥接器
Adapter 和 Bridge 模式有一些共同的属性。两者都通过提供对另一个对象的间接级别来提高灵活性。两者都涉及将请求从它自己的接口以外的接口转发到该对象。
这些模式之间的主要区别在于它们的意图。适配器专注于解决两个现有接口之间的不兼容性。它不关注这些接口是如何实现的,也不考虑它们如何独立发展。这是一种让两个独立设计的类一起工作而无需重新实现一个或另一个的方法。另一方面,桥接了一个抽象及其(可能有很多)实现。它为客户端提供了一个稳定的接口,即使它允许您改变实现它的类。随着系统的发展,它还适应新的实现。
由于这些差异,适配器和桥接器经常在软件生命周期的不同点使用。当您发现两个不兼容的类应该一起工作时,通常需要使用适配器,通常是为了避免复制代码。耦合是不可预见的。相比之下,桥的用户预先了解抽象必须具有多个实现,并且两者都可以独立发展。适配器模式使事情在设计之后就可以工作;Bridge 让它们先于它们工作。这并不意味着 Adapter 在某种程度上不如 Bridge。每种模式仅解决不同的问题。
桥接器是改进的适配器。Bridge 包括适配器并为其增加了额外的灵活性。以下是 Ravindra 答案中的元素如何在模式之间映射:
Adapter | Bridge
-----------|---------------
Target | Abstraction
-----------|---------------
| RefinedAbstraction
|
| This element is Bridge specific. If there is a group of
| implementations that share the same logic, the logic can be placed here.
| For example, all cars split into two large groups: manual and auto.
| So, there will be two RefinedAbstraction classes.
-----------|---------------
Adapter | Implementor
-----------|---------------
Adaptee | ConcreteImplementor
根据此处的另一个stackoverflow答案,对我来说似乎更短更清晰的答案:
当您有一个抽象接口并且您希望将该接口映射到另一个具有相似功能但接口不同的对象时,将使用适配器。
Bridge与 Adapter 非常相似,但是当您同时定义抽象接口和底层实现时,我们将其称为 Bridge。即,您不适应某些遗留或第三方代码,您是所有代码的设计者,但您需要能够交换不同的实现。
假设您有一个具有(通用/抽象)绘图功能的抽象 Shape 类和一个实现 Shape 的 Circle。桥接模式只是一种将实现(在 Circle 中绘图)和通用/抽象功能(在 Shape 类中绘图)解耦的双向抽象方法。
它的真正含义是什么?乍一看,这听起来像是您已经在制作的东西(通过依赖倒置)。所以不用担心拥有更少或更多模块化的代码库。但它背后有更深层次的哲学。
据我了解,当我需要添加与当前系统密切相关的新类(如 RedCircle 或 GreenCircle )并且它们只有一个功能(如 color )不同时,可能会出现使用模式的需要。而且我将需要 Bridge 模式,特别是如果要经常更改现有系统类( Circle 或 Shape )并且您不希望新添加的类受到这些更改的影响。这就是为什么通用绘图功能被抽象到一个新接口中的原因,这样您就可以独立于 Shape 或 Circle 改变绘图行为。
有很多答案可以区分适配器和桥接器。但是当人们在寻找代码示例时,我将举一个适配器设计模式的示例,将其制作成时间线故事:
//---------------------------------------External Vendor/Provider--------------------------------
//Adaptee | RussianTankInterface is adaptee | adaptee lives in is own lala land and do not care about any other class or interface
RussianTankInterface smerch9K58 = new RussianTank("The Russian Artillery bought by India in October 2015");
smerch9K58.aboutMyself();
smerch9K58.stuff();
smerch9K58.rotate();
smerch9K58.launch();
//---------------------------------2016 : India manufactures Bharat52 ------------------------------
//Client_1 :: IndianTank
EnemyAttacker bharat52Attacker = new IndianTank("Tank built in India delivered to Army in Jul 2016");
// behaves normally -------------------------(1)
bharat52Attacker.aboutMe();
bharat52Attacker.load();
bharat52Attacker.revolve();
bharat52Attacker.fireArtillery();
//---------------------------------2019 : India mnufactures Pinaka, and thought about fusion with Russian technology - so adaption required ------------------------------
//Client_2 :: IndianTank
EnemyAttacker pinakaAttacker = new IndianTank("Tank built in India in 1998 got upgraded_v1 in 9 Sep 2019");
#####----Bilateral-Coalition happens----##
##### India : I want a fusion artillery technology with
##### 1) Indian materials and brain-power but
##### 2) Russian machine-parts-movement technology
##### Russia : Give me your Interface - at max we can help by providing an Adapter
//---------------------------------------External Vendor/Provider-----------------------------------
//Adapter :: RussianTechnologyAdapter | Russia gets EnemyAttacker interface only from India & creates RussianTechnologyAdapter
RussianTechnologyAdapter russianTechnologyAdapter = new RussianTechnologyAdapter(smerch9K58);
//Target | EnemyAttacker was initially ClientInterface but later becomes the Target as story evolves | <- client owns this Interface
EnemyAttacker dhanushAttacker = russianTechnologyAdapter;
#####----Russia keeps her Word----##
##### Russia to India : Here you go! Take Dhanush, a wrapper over our encapsulated adapter, and plug-in anything conforming to your EnemyAttacker.
##### India : Thanks a lot!
//--------------------------------- 2020 : India returns back happily with dhanushAttacker---------------------------------------
//Client_2 - adapted behavior -------------------------(2)
dhanushAttacker.setNavigationCapability(pinakaAttacker.getCuttingEdgeNavigableTargets());
dhanushAttacker.aboutMe(); //calls RussianInstance -> aboutMyself()
dhanushAttacker.load(); //calls RussianInstance -> stuff()
dhanushAttacker.revolve(); //calls RussianInstance -> rotate()
dhanushAttacker.fireArtillery(); //calls RussianInstance -> launch()
仔细注意:
- (1) 和 (2) 中的相同 API 行为不同
- 印度可以将其大脑推入俄罗斯的线框,例如
dhanushAttacker.setNavigationCapability(pinakaAttacker.get(..))
值得注意的点
客户端拥有:
- Invoker /Use(uses Adapter later point)/Client
- ClientInterface (a.k.a Target )
稍后分享:
- ClientInterface ( becomes Target after sharing)
接收方拥有:
- Adapter (later shared directly or as a wrapper )
- Adaptee
希望有人也为 Bridge 提供内联:)
我认为这很简单。
该适配器旨在允许第三方应用程序与您的应用程序一起工作。相反,这样您的应用程序就可以与第三方应用程序一起使用。
使用桥接模式,它应该连接两个或多个应用程序而无需实现适配器。
实际上,桥接器是两个应用程序将通过它进行交互的接口。作为桥接的一个例子,这些是 PHP 中的 PSR 接口。
例子:
其他应用
<?php
interface IRequestDataOtherApp {};
interface IResponseDataOtherApp {};
class ResponseDataOtherApp implements IResponseDataOtherApp {
};
class OtherApp {
public static function request(IRequestDataOtherApp $requestData):IResponseOtherApp
{
// code
return new ResponseDataOtherApp ();
}
}
我的应用
<?php
interface IResponseDataMyApp {};
interface IReqestDataMyApp {};
class ReqestDataMyApp implements IReqestDataMyApp {};
class Adapter {
public static function convertResponseData(IResponseDataOtherApp $response):IResponseDataMyApp
{
}
public static function convertRequestData(IReqestDataMyApp $request):IRequestOtherApp
{
}
};
$unformattedResponse=OtherApp::request(Adapter::convertRequestData(new ReqestDataMyApp ()));
$myResponse=ResponseAdapter::convertResponseData($unformattedResponse);
//...
在前面的示例中,我们为每个请求和每个响应实现了 2 个适配器。如果我们重写示例并尝试实现桥接。
<?php
interface IBridgeResponse {};
其他应用
<?php
interface IRequestDataOtherApp {};
interface IResponseDataOtherApp {};
class ResponseDataOtherApp implements IBridgeResponse, IResponseDataOtherApp {
};
class OtherApp {
public static function request(IRequestDataOtherApp $requestData):IResponseOtherApp
{
// code
return new ResponseDataOtherApp ();
}
}
我的应用
<?php
interface IResponseDataMyApp {};
interface IReqestDataMyApp {};
class ReqestDataMyApp implements IReqestDataMyApp {};
class Adapter {
public static function convertResponseData(IResponseDataOtherApp $response):IResponseDataMyApp
{
}
public static function convertRequestData(IReqestDataMyApp $request):IRequestOtherApp
{
}
};
$response=OtherApp::request(Adapter::convertRequestData(new ReqestDataMyApp ()));
if($response instanceof IBridgeResponse){
/**
The component has implemented IBridgeResponse interface,
thanks to which our application can interact with it.
This is the bridge.
Our application does not have to create an additional adapter
(if our application can work with IBridgeResponse).
*/
}
//...
在六边形架构中,如果您从一开始就编写应用程序并准备好接受正在使用的另一个应用程序的规则,则端口(接口、合同)可以充当“桥梁”。否则,您将不得不编写“适配器”。
Bridge 与 Adapter 非常相似,但是当您同时定义抽象接口和底层实现时,我们将其称为 Bridge,这意味着不适应外部代码,您是所有代码的设计者,但您需要能够换出不同的代码实施。