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

Posted

技术标签:

【中文标题】桥接模式和适配器模式之间的区别【英文标题】:Difference between Bridge pattern and Adapter pattern 【发布时间】:2010-11-28 08:26:20 【问题描述】:

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

【问题讨论】:

或许可以考虑提供一个澄清编辑来指导讨论您认为需要在哪里使用其中一个。 ***.com/questions/350404/… 这里没有任何解释可以取代Design Patterns: Elements of Reusable Object-Oriented Software的阅读 【参考方案1】:

“适配器在设计完成后使它们工作;Bridge 使它们 在他们之前工作。 [GoF,第 219 页]"

实际上,Adapter 模式在您拥有现有代码(无论是第三方代码还是内部代码)但不受您控制或无法更改以完全满足您需要的接口时很有用它到。例如,我们有一个 SuperWeaponsArray,它可以控制一组精细的末日设备。

public class SuperWeaponsArray 
  /*...*/

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

太好了。除了我们意识到我们的武器库中有一个核装置,它远远早于武器界面的转换。但我们真的很希望它在这里工作......所以我们该怎么办......把它楔入!

NukeWeaponsAdaptor - 基于我们的 Nuke 类,但导出了 Weapon 接口。亲爱的,现在我们肯定可以毁灭世界了。这似乎有点杂乱无章,但它使事情顺利进行。


Bridge 模式是你预先实现的——如果你知道你有两个正交的层次结构,它提供了一种解耦接口和实现的方法,这种方式是你无法理解的数量惊人的课程。假设您有:

MemoryMappedFile 和 DirectReadFile 类型的文件对象。假设您希望能够从各种来源(可能是 Linux 与 Windows 实现等)读取文件。 Bridge 可帮助您避免清盘:

MemoryMappedWindows 文件 MemoryMappedLinux文件 直接读取Windows文件 DirectReadLinuxFile

【讨论】:

投了反对票,你能用更抽象的代码清单吗?这个例子太具体了,令人困惑。 @omouse 赞成,示例 code 确实不是让这个答案切中要害的原因。对于细心的读者来说,有足够的指针来开始区分模式,所以总而言之 - 它一个很好的答案。 能否提供一些实际的桥接模式代码示例? 我想很多人都以与我相同的方式解决了这个问题 - 他们可能已经在研究这两种模式的代码,但认识到一些相似之处并意识到他们的理解可以通过将两者并置来进一步巩固模式。至少对我而言,关于桥接帮助您避免使用 Windows 和 Linux 特定文件的那句话有助于理解桥接模式的“实施者”(dofactory.com/net/bridge-design-pattern) 与“适配器”的不同之处。 “Adapter 让事情在设计之后就可以工作;Bridge 让他们在设计之前就可以工作。” 在我读的书中根本没有具体说明,所以它是很难区分两者。我想阅读GOF毕竟值得努力......【参考方案2】:

http://en.wikipedia.org/wiki/Adapter_pattern

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

如果您想将一组公司标准的 Web 服务 API 提供给另一个应用程序的现有可扩展性接口,您可以考虑编写一组适配器来执行此操作。请注意,有一个灰色区域,这更多是关于您如何在技术上定义模式,因为外观等其他模式是相似的。

http://en.wikipedia.org/wiki/Bridge_pattern

桥接模式将允许您可能拥有算法或系统的替代实现。

虽然不是典型的桥接模式示例,但想象一下,如果您有几个数据存储实现:一个在空间上高效,另一个在原始性能上高效......并且您有一个业务案例可以在您的应用程序或框架。

就您的问题“我可以在哪里使用哪种模式”而言,答案是,只要它对您的项目有意义!或许可以考虑提供一个澄清性编辑来指导您认为需要在哪里使用其中一个或另一个的讨论。

【讨论】:

【参考方案3】:

适配器:

    这是一种结构模式 使用两个不兼容的接口很有用

UML 图:来自dofactory 文章:

目标:定义客户端使用的特定领域接口。

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:它实现了抽象:

    Implementor:定义了实现的接口

    ConcreteImplementor:实现了Implementor接口。

代码sn-p:

Gear gear = new ManualGear();
Vehicle vehicle = new Car(gear);
vehicle.addGear();

gear = new AutoGear();
vehicle = new Car(gear);
vehicle.addGear();

相关帖子:

When do you use the Bridge Pattern? How is it different from Adapter pattern?

主要区别:来自sourcemaking文章

    适配器在设计完成后就可以工作; Bridge 让他们在他们之前工作。 Bridge 是预先设计的,可以让抽象和实现独立变化。对适配器进行了改造,以使不相关的类可以协同工作。

【讨论】:

在答案中包含文档中的汽车/卡车/齿轮示例。很好的例子和类比。【参考方案4】:

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

【讨论】:

虽然,看起来超出了这个问题的范围,但对 Facade 加权适配器和桥可能非常合适。【参考方案5】:

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

适配器与桥接器

适配器和桥接模式有一些共同的属性。两者都通过提供对另一个对象的间接级别来提高灵活性。两者都涉及 将请求从它自己的接口以外的接口转发到此对象。

这些模式之间的主要区别在于它们的意图。适配器专注于 解决两个现有接口之间的不兼容问题。它不关注如何 这些接口已实现,也不考虑它们如何独立发展。这是一种让两个独立设计的类一起工作的方法 无需重新实现其中之一。另一方面,桥接了一个抽象及其(可能有很多)实现。它提供了一个稳定的接口 即使它允许您改变实现它的类,也可以向客户提供。它还容纳 随着系统的发展,新的实现。

由于这些差异,Adapter和Bridge经常用在不同的点上 在软件生命周期中。当您发现 两个不兼容的类应该一起工作,通常是为了避免复制代码。这 耦合是不可预见的。相比之下,网桥的用户预先了解 抽象必须有几个实现,并且两者都可以独立发展。 适配器模式使事情在设计之后工作;桥使他们 之前工作。这并不意味着 Adapter 在某种程度上不如 Bridge。每个 模式只是解决了一个不同的问题。

【讨论】:

【参考方案6】:

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

【讨论】:

【参考方案7】:

根据另一个 *** 答案here,对我来说似乎更短更清晰的答案:

Adapter 用于当你有一个抽象接口,并且你想 将该接口映射到另一个具有类似功能的对象 角色,但界面不同。

Bridge 与 Adapter 非常相似,但当你使用时我们称它为 Bridge 定义抽象接口和底层实现。 IE。你没有适应一些遗留或第三方代码,你是 所有代码的设计者,但您需要能够换出 不同的实现。

【讨论】:

【参考方案8】:

假设您有一个具有(通用/抽象)绘图功能的抽象 Shape 类和一个实现 Shape 的 Circle。桥接模式只是一种将实现(在 Circle 中绘图)和通用/抽象功能(在 Shape 类中绘图)解耦的双向抽象方法。

这到底是什么意思?乍一看,这听起来像是您已经在制作的东西(通过依赖倒置)。所以不用担心拥有更少或更多模块化的代码库。但这背后有更深层次的哲学。

根据我的理解,当我需要添加与当前系统密切相关的新类(如 RedCircle 或 GreenCircle )并且它们只有一个功能(如 color )不同时,可能会出现使用模式的需求。而且我将需要 Bridge 模式,特别是如果要经常更改现有系统类( Circle 或 Shape )并且您不希望新添加的类受到这些更改的影响。这就是为什么将通用绘图功能抽象到一个新接口中的原因,这样您就可以独立于 Shape 或 Circle 改变绘图行为。

【讨论】:

【参考方案9】:

有很多答案可以区分适配器和桥接器。 但是由于人们正在寻找代码示例,我将举一个适配器设计模式的示例,将其制作成时间线故事:

    //---------------------------------------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 提供内联 :)

【讨论】:

【参考方案10】:

Bridge 与 Adapter 非常相似,但是当您同时定义抽象接口和底层实现时,我们称之为 Bridge,意味着不适应外部代码,您是所有代码的设计者,但您需要能够换出不同的实现。

【讨论】:

【参考方案11】:

我觉得很简单。

适配器旨在允许第三方应用程序与您的应用程序一起工作。 反之,您的应用程序可以与第三方应用程序一起使用。

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

实际上,桥接器是两个应用程序通过它进行交互的接口。作为桥接的一个例子,这些是 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).
*/

//...

在六边形架构中,如果您从一开始就编写应用程序并准备好接受正在使用的另一个应用程序的规则,端口(接口、合同)可以充当“桥梁”。 否则,您将不得不编写“适配器”。

【讨论】:

以上是关于桥接模式和适配器模式之间的区别的主要内容,如果未能解决你的问题,请参考以下文章

设计模式之适配器模式与桥接模式详解和应用

桥接模式和适配器模式

Java设计模式之四 ----- 适配器模式和桥接模式

Java进阶篇设计模式之四 -----适配器模式和桥接模式

适配器模式和装饰器模式有什么区别?

设计模式之桥接模式(Bridge)