代码之家  ›  专栏  ›  技术社区  ›  Charles Graham

你什么时候用桥型?它与适配器模式有何不同?

  •  139
  • Charles Graham  · 技术社区  · 16 年前

    Bridge Pattern 在真实的应用程序中?如果是,你是如何使用它的?是我,还是只是适配器模式,在混合中加入了一点依赖注入?它真的应该有自己的模式吗?

    11 回复  |  直到 7 年前
        1
  •  264
  •   Top-Master OMG Ponies    3 年前

    下面是我的联合收割机 Federico's John's 答案:

    这个 Bridge composition 结束 inheritance ".

    当我们有:

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

    重构层次结构,如:

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

    换句话说,创建一个属性(例如,用于获取/设置颜色),而不是使用一个长的 subclass 和/或 interface 链(增加颜色)。

    值得一提的是,确认了 分离 将调用接口(例如颜色要求) Adapter

        2
  •  237
  •   Federico A. Ramponi    16 年前

    桥接模式是一个古老建议的应用,“宁可组合也不要继承”。 当您必须以相互正交的方式对不同的时间进行子类化时,它就变得很方便了。假设您必须实现彩色形状的层次结构。你不会用矩形和圆形来划分形状的子类,然后用红色矩形、蓝色矩形和绿色矩形来划分矩形的子类,而用圆形来划分矩形的子类,对吗?你更愿意说每个形状

        3
  •  223
  •   Alex Lockwood Nigel Hawkins    12 年前

    什么时候:

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

    重构到:

         A         N
      /     \     / \
    Aa(N) Ab(N)  1   2
    
        4
  •  79
  •   shek    16 年前

    Bridge pattern Wikipedia entry composite Template Strategy 模式。

    在桥接模式中,适配器模式的某些方面是常见的。然而,引用 this article

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

        5
  •  31
  •   thSoft    10 年前

    根据我的经验,桥接是一种经常重复出现的模式,因为它是解决方案 域中有两个正交维度

    还有一个建议:始终考虑设计模式 从概念的角度 ,而不是从实现的角度。从正确的角度来看,桥接器不能与适配器混淆,因为它们解决了一个不同的问题,而且组合优于继承并不是因为它本身,而是因为它允许单独处理正交关注点。

        6
  •  30
  •   Bill Karwin    16 年前

    我看到的解释是,当您试图统一某些不兼容类的接口时,会使用适配器 . 适配器的功能是作为一种转换器,转换到可以考虑的实现中 .

    而桥接模式用于更可能是新域的代码。您正在设计网桥,以便为需要更改的实现提供抽象接口,但也定义了这些实现类的接口。

    设备驱动程序是一个经常被引用的桥接示例,但如果您为设备供应商定义接口规范,我会说它是一个桥接,但是如果您使用现有设备驱动程序并创建包装类来提供统一接口,它是一个适配器。

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

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

        7
  •  23
  •   Ravindra babu    7 年前

    意图 适配器

    1. 抽象和实现在编译时不受约束
    2. 抽象和实现-两者都可以在不影响客户端的情况下变化
    3. 使用组合而不是继承。

    在以下情况下使用桥接模式:

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

    @JohnSonmez的回答清楚地表明了桥接模式在减少类层次结构方面的有效性。

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

    适配器模式 :

    1. 信息技术 允许两个不相关的接口一起工作
    2. 它修改了原始接口。

    1. 适配器 让他们在工作之前就开始工作。
    2. 设计在前端,让 . 适配器 对其进行了改造,使不相关的类可以一起工作。
    3. 适配器 允许两个不相关的接口一起工作。 允许抽象和实现独立变化。

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

    Difference between Bridge pattern and Adapter pattern

    有用的文章:

    sourcemaking bridge

    sourcemaking adapter 范文

    journaldev bridge 范文

    桥接模式将抽象与实现分离,以便两者可以独立变化。它是通过组合而不是继承实现的。

    Wikipedia中的桥接模式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. Car Truck 是的两个具体实现 交通工具 .
    3. 交通工具 addGear() .
    4. Gear
    5. ManualGear AutoGear 排挡
    6. 交通工具 implementor Compositon 实现者接口的定义是该模式的关键:
    7. addGear() -或者 Manual Auto

    桥接模式的用例 :

    1. 抽象 实施 可以相互独立地更改,并且它们在编译时不受约束
    2. 映射正交层次结构-一个用于 一个给我 实施
        8
  •  9
  •   Dave M NPE    12 年前

    我在工作中使用了桥接模式。我在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. pImpl 输入到.cpp文件中。这使您能够保持 Aimpl 标头私有,并减少编译时间。

        9
  •  7
  •   NotAgain    11 年前

    #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
  •  1
  •   Sylvain Rodrigue    5 年前

    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
  •  0
  •   j2emanue    12 年前

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

        12
  •  -5
  •   sohan kumawat    7 年前
    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);
    }
    }
    }