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

172

桥接模式和适配器模式有什么区别?


也许可以考虑提供一份澄清编辑,以指导讨论何时需要使用其中之一。 - Jeff Wilcox
https://dev59.com/l3RC5IYBdhLWcg3wSu57 - Andrei
任何解释都无法替代阅读《设计模式:可复用的面向对象软件元素》(https://www.amazon.com/dp/0201633612/ref=cm_sw_em_r_mt_dp_U_JJaPEbYC44N2W)。 - lealceldeiro
11个回答

209

"适配器模式是在设计完成之后使事情能够运转;桥接模式则是在设计时就使它们工作。[GoF, p219]"

适配器模式(Adapter pattern)通常在你拥有现有代码但无法对其进行更改以满足所需接口的情况下非常有用,这些代码可能来自第三方或内部,但不在您的控制范围内。例如,我们有一个超级武器阵列(SuperWeaponsArray),可以控制一组毁灭性设备。

public class SuperWeaponsArray {
  /*...*/

  public void destroyWorld() {
    for (Weapon w : armedWeapons) {
      w.fire();
    }
  }
}

很好。除了我们意识到我们的武器库中有一枚核弹头比转换成Weapon接口早得多。但是我们真的想在这里让它工作...所以我们该怎么办... 把它塞进去!

NukeWeaponsAdaptor - 基于我们的Nuke类,但导出Weapon接口。太棒了,现在我们肯定可以摧毁世界了。这似乎有点笨拙,但确实使事情变得可行。


桥接模式是一种您提前实施的东西-如果您知道有两个正交层次结构,则它提供了一种将接口和实现解耦的方式,以便您不会得到疯狂数量的类。假设您有:

MemoryMappedFile和DirectReadFile类型的文件对象。假设您想能够从各种来源读取文件(可能是Linux vs. Windows实现等)。桥接帮助您避免最终得到的结果是:

MemoryMappedWindowsFile MemoryMappedLinuxFile DirectReadWindowsFile DirectReadLinuxFile


15
被踩了,能否使用更抽象的代码清单?这个例子太具体了,令人困惑。 - user9903
44
@omouse 点赞了,例子代码并不是这个答案的关键所在。仔细阅读可以找到足够的指引来开始区分模式,总的来说 - 这是一个好答案。 - Victor Farazdagi
21
可以为您提供桥接模式的一些实际代码示例。 - Jaime Hablutzel
2
我想许多人跟我一样是通过查看这两种模式的代码后发现相似之处,进而意识到通过将这两种模式并置来巩固理解是非常必要的。至少对我来说,关于桥接模式如何帮助避免生成特定于Windows和Linux的文件的那句话在理解"实现者"(http://www.dofactory.com/net/bridge-design-pattern)与"适配器"的区别时起了重要作用。 - Jordan
4
“适配器(Adapter)让事物在设计完成后工作;桥接器(Bridge)让它们在设计之前就能工作。”这句话在我读的书籍中没有被明确说明,所以很难区分这两个模式。我想读GOF的书还是值得的。 - Alexander Derck

18

适配器:

  1. 它是一种结构型模式
  2. 它有助于处理两个不兼容的接口

例子:

正方形和矩形是两种不同的形状,获取它们各自的面积()需要不同的方法。但是,仍然可以通过转换一些属性,使正方形适用于矩形接口。

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);
    }
}

桥接模式:

  1. 它是一种结构型模式
  2. 它将抽象与其实现分离,以便两者可以独立变化

Gang of Four 设计模式书籍(第174页) 所解释的,此模式有四个组件:

Abstraction:定义了抽象的接口

RefinedAbstraction:扩展了 Abstraction 定义的接口

Implementor:为实现类定义了接口

ConcreteImplementor:实现 Implementor 接口并定义其具体实现。

请参考下面的 Stack Exchange 帖子中的代码:

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


1
在答案中包含文档中的汽车/卡车/齿轮示例。非常好的例子和比喻。 - quasoft
Square和Rect适配器示例无效。您只需删除"extends"而不改变代码含义,就可以这样做: class SquareArea { ... } - Evgeny

18

适配器模式(Adapter pattern)

适配器模式更多的是让现有代码与新系统或接口一起使用。

如果您有一组公司标准的Web服务API,希望提供给另一个应用程序的现有可扩展性接口,则可以考虑编写一组适配器来实现。需要注意的是,适配器模式和外观模式等其他模式相似,技术上如何定义模式存在一定灰色地带。

桥接模式(Bridge pattern)

桥接模式可以让您拥有算法或系统的替代实现。

尽管不是经典的桥接模式示例,但想象一下,如果您有一些数据存储的实现:其中一个在空间效率方面高效,另一个在原始性能方面高效……并且您有业务需求在应用程序或框架中都提供这两个实现。

至于您的问题“在哪里使用哪种模式”,答案是:无论何时在您的项目中合理使用即可!也许可以考虑提供一次澄清编辑,以指导讨论关于何时使用适配器模式或桥接模式。


11
在最佳答案中,@James引用了从GoF第219页摘录的一句话。我认为在这里复制完整解释是值得的。

适配器模式与桥接模式

适配器模式和桥接模式具有一些共同点。它们都通过提供对另一个对象的间接性来促进灵活性。它们都涉及从不同于其自身的接口转发请求。

这些模式之间的关键区别在于它们的意图。适配器专注于解决两个现有接口之间的不兼容性。它不关注这些接口的实现方式,也不考虑它们可能会独立地发展。它是使两个独立设计的类协同工作的一种方法,而无需重新实现其中一个。另一方面,桥接模式桥接了一个抽象和它的(潜在众多)实现。它为客户端提供一个稳定的接口,同时允许你变化实现该接口的类。随着系统的发展,它还能容纳新的实现。

由于这些差异,适配器模式和桥接模式通常在软件生命周期的不同阶段使用。当你发现两个不兼容的类应该协同工作时,通常需要适配器模式,以避免复制代码。这种耦合是意想不到的。相反,桥接模式的用户预先了解抽象和其实现之间的关系。

抽象化必须具有多个实现,两者可以独立演进。适配器模式使得设计完成后事情能够工作;桥接模式使它们在被设计前就能够工作。这并不意味着适配器比桥接模式更差;每种模式只是解决不同的问题。

1
这里的主要句子是:“适配器模式使得事物在被设计后能够工作;桥接模式则使它们在被设计前就能够工作。”说得非常好 :) - MrVasilev

8
这篇文章已经存在了相当长的时间。然而,重要的是要理解,门面模式与适配器有些相似,但并不完全相同。适配器将一个现有的类适配到通常不兼容的客户端类上。例如,假设您的应用程序作为客户端使用旧工作流系统。您的公司可能会用一种新的“不兼容”的工作流系统(就接口而言)来替换旧的工作流系统。在大多数情况下,您可以使用适配器模式编写代码,实际上调用新工作流引擎的接口。桥接模式通常以不同的方式使用。如果您确实有一个需要与不同文件系统(例如本地磁盘、NFS等)协作的系统,您可以使用桥接模式,并创建一个抽象层来处理所有文件系统。这基本上是桥接模式的一个简单用例。门面和适配器确实具有一些共同点,但门面通常用于简化现有接口/类。在 EJB 的早期,没有本地调用 EJB。开发人员总是获取存根,缩小范围并称其为“伪远程调用”。这通常会导致性能问题(特别是在真正通过网络进行调用时)。有经验的开发人员会使用门面模式向客户端提供一个非常粗粒度的接口。然后,这个门面将进一步调用不同的更细粒度方法。总的来说,这大大减少了所需的方法调用数量并提高了性能。

1
虽然看起来超出了这个问题的范围,但是将适配器和桥接器与外观模式进行权衡可能非常合适。 - Cody

5

根据另一个stackoverflow的回答,对我来说这是更短更清晰的答案这里:

  • Adapter 是在你有一个抽象接口并且你想将该接口映射到另一个具有类似功能但不同接口的对象时使用。

  • Bridge 和Adapter非常相似,但当你定义抽象接口和底层实现时,我们称其为Bridge。也就是说,你不是适应一些旧代码或第三方代码,而是所有代码的设计师,但需要能够交换不同的实现。


3

桥接器是改进后的适配器。桥接器包括适配器,并为其添加了额外的灵活性。 以下是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

1
桥接模式与适配器模式非常相似,但当您定义抽象接口和底层实现时,我们称其为桥接模式,这意味着不适应外部代码,您是所有代码的设计师,但需要能够更换不同的实现。

0
假设您有一个抽象的形状类,其中包含(通用/抽象)绘图功能和实现形状的圆形。桥接模式只是一种双向抽象方法,用于解耦实现(在圆形中绘制)和通用/抽象功能(在形状类中绘制)。这到底意味着什么?乍一看,它听起来像是您已经通过依赖反转实现的东西。因此,不必担心代码库变得不够严格或更加模块化。但是,背后有一种更深层次的哲学。
据我理解,当我需要添加与当前系统密切相关的新类(例如RedCircle或GreenCircle),并且它们仅通过单个功能(例如颜色)进行区分时,可能会出现使用模式的需求。如果现有的系统类(Circle或Shape)经常更改,并且您不希望新添加的类受到这些更改的影响,那么我将需要桥接模式。因此,通用绘图功能被抽象成一个新接口,以便您可以独立于Shape或Circle更改绘图行为。

0

我认为这很简单。

适配器的设计是为了让第三方应用程序能够与您的应用程序一起工作。反之亦然,以便您的应用程序可以与第三方应用程序一起工作。

使用桥接模式,它应该连接两个或多个应用程序而不实现适配器。

事实上,桥接是两个应用程序将相互交互的接口。作为桥接的示例,这些是 PHP 中的 PSR 接口。

例如:

OtherApp

<?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).
*/
}
//...

在六边形架构中,端口(接口、契约)可以充当“桥梁”,如果你从一开始就编写应用程序,并且愿意接受使用另一个应用程序的规则。 否则,你将不得不编写“适配器”。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接