桥接模式 vs 策略模式

11

我知道,这个问题已经被问了很多次,但是我做了一些研究,仍然不明白,也许你可以帮帮我:

就像之前所说的那样,UML几乎是相同的。实现和思想也或多或少相同:不使用子类型,而是定义一个接口,它封装了一些逻辑并将其传递给一个抽象类。

因此,即使是微软博客的人们也https://blogs.msdn.microsoft.com/gyanjadal/2015/01/05/difference-between-strategy-and-bridge-patterns/说:

简单的答案是“它们相似但不同”。实现虽然相似,但意图不同。打个比方,城市公交车和校车都是相似的车辆,但用途不同。一个用于在城市各地之间运输人员,作为通勤服务。另一个用于将孩子送到学校。

“如果它听起来像鸭子,看起来像鸭子,但它的意图是天鹅,那么它可能是它们中的任何一个”,这是我在这里读到的。

由于我仍然没有理解,所以我深入挖掘:

https://social.msdn.microsoft.com/Forums/en-US/08775d39-2de0-4598-8872-df21f681b7b3/strategy-vs-bridge-patterns?forum=architecturegeneral

这个线程没有添加任何新内容,除了:

它们在表面上看起来对我来说是一样的。我看到的主要区别在于桥接模式中抽象部分是对象的一部分,但在策略模式中抽象是由对象执行。

但是,如果我们阅读策略的定义:

定义一组算法,将每个算法封装起来,并使它们可以互换。策略允许算法独立于使用它的客户端而变化。

并没有定义策略如何应用。它也可以很容易地成为抽象的接口,就像常见的Strategy-Implementation,例如LINQ-Orderby等。

关于这个主题的另一个有趣观点在这里:

http://game-engineering.blogspot.ch/2008/07/bridge-pattern-vs-strategy-pattern.html

这篇文章的主要内容是:
当您想要改变行为时,可以通过引入类继承关系而不是编写不同对象来使用“策略”模式。当您期望界面和实现都会变化时,可以使用“桥接”模式。在这两种情况下,都提供了对于可变实现的灵活性;在桥接模式中,您还期望界面发生变化。
这可能是两者之间的主要区别。由于实现者和抽象层松散耦合,因此我可以更改实现者的接口,而抽象层则不必关心这个变化?这听起来很合理,但难道抽象层也不需要更改吗,因为它们有某种联系吗?那样做不会破坏其他原则,如信息隐藏和DRY原则吗?
我还查看了许多例子,出于空间考虑,我不在此处添加,但我无法找到任何一个例子,其中一个模式无法更改以适应另一个模式。无论是通过接口属性还是仅通过参数。
我有遗漏吗?是否有人有一个真实的例子,“我想使用策略模式,但桥接模式更加合适”,或者反过来的例子?
编辑:为什么我要为这个主题再次正式开一个帖子呢?首先,提到的帖子中被接受的答案如下:
“据我所知,当您从外部源(例如,配置可以指定加载某些插件程序集)抽象出可能提供的行为时,您正在使用策略模式,当您使用相同的构造使代码变得更加整洁时,您正在使用桥接模式。实际的代码看起来非常相似-您只是为稍微不同的原因应用了这些模式。”
我已经在以前的解释中提供了从外部源中抽象行为的定义,这恰好是策略和桥接模式的定义。
此外,
“当您使用相同的构造使代码变得更加整洁时,您正在使用桥接模式。”
此外,策略模式使代码变得更加整洁,因为它将一个完整的构建块抽象掉,从而大大缩短了代码。
我认为任何阅读整个主题的人都会发现,这个主题不仅仅是这两个句子。

7
说实话,我从来没有说过“我想使用某种模式”。我解决问题后,再使用模式向其他开发人员描述我所做的。 - Eris
2
可能是桥接模式和策略模式的区别是什么?的重复问题。 - Eris
1
此外:https://dev59.com/Km025IYBdhLWcg3wr4B6?rq=1 - Eris
坦率地说,https://dev59.com/a3RB5IYBdhLWcg3w7riV#464549 上被接受的答案并不是一个很好的答案(它引用了维基百科,关于 UML 的信息是错误的,而且早就被从页面上删除了)。 - Fuhrmanator
1
《Head First 设计模式》中的“远程控制器”示例是 Bridge 模式的一个很好的解释。在该示例中,它更接近于 Command 模式而不是 Strategy 模式。 - Fuhrmanator
显示剩余2条评论
3个回答

3

我查看了原始的设计模式书籍,以了解作者如何定义桥接模式。他们的现实生活示例展示了一种情况,即抽象和实现层次结构都可以独立地更改(即可以为抽象引入新的子类;也可以为实现引入新的子类)。他们的示例涉及窗口库,可以适用于不同的窗口系统。

在原始示例中,作者使用了来自IBM的不同窗口系统,但我认为一个好的当前类比是不同的Linux窗口管理器(GNOME、KDE等)。

因此,想象一个Window抽象,并为GNOME和KDE提供两个实现。现在想象一下,您想添加新的Window子类TransparentWindow。TransparentWindow扩展Window,就像GNOMEWindow和KDEWindow一样。但是,您还必须为TransparentWindow提供实现:GNOMETransparentWindow和KDETransparentWindow。层次结构开始变得混乱。

想象一下新类型的窗口或新的窗口管理器——XFCE。为避免复杂的层次结构,他们引入了桥接模式,并使这两个层次结构分开(即TransparentWindow扩展Window;GNOMEWindow和KDEWindow扩展WindowImpl)。

对我来说,似乎棘手的部分是为实现定义一个接口,因为抽象层次结构将需要仅使用该接口来定义其操作。

我喜欢的桥接模式的学习示例是这里,我喜欢它是因为它不使用人工类ConcreteImplementor1和ConcreteImplementor2。当涉及到现实生活中的例子时,我相信我在Selenium WebDriver实现中看到了这种模式,但现在我不确定了。


非常感谢,对我来说,一个模式如此努力地反对所有已知的原则,这似乎很奇怪:如果实现和层次结构彼此了解得如此之少,那么外部的某个人需要知道传递什么。因此,封装、高内聚性甚至松散耦合都消失了。 - Matthias Müller
TransparentWindow 知道它必须绘制边框,并将背景颜色设置为透明。WindowImpl 有一个方法来绘制边框和设置背景颜色。GNOMEWindowImpl 知道如何在 GNOME 窗口管理器中绘制边框和设置背景。GNOMEWindowImpl 不知道这两个操作将被调用来绘制透明窗口。因此,不是抽象和实现彼此之间了解得太少,而是它们都知道自己需要知道的内容 :) - oldbam
嗯,好的,正如之前的评论所述,主要区别似乎实际上是正交层次结构。基本上,策略模式也可以做到这一点,但桥接模式确切地为此而设计。 - Matthias Müller

3

桥接模式的维基百科UML图示:

桥接模式的维基百科UML图示请查看我在链接问题中的回答,了解基本区别:

桥接模式和策略模式的差异是什么?

主要区别:抽象和实现可以独立变化。

关于您的其他问题:

这可能是主要区别吗?因为实现和抽象解耦合得如此之紧密,我可以更改实现者的接口而不必担心抽象是否需要更改?听起来合理,但不是抽象也需要更改,因为它们有某种联系吗?

请查看以下代码示例 @

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

即使示例是用Java编写的,C#开发人员也可以轻松理解。

在链接的示例中:

Vehicle            : Abstraction
Car                : Re-defined Abstraction
Truck              : Re-defined Abstraction
Implementor        : GearShifter
ConcreteImplementor: ManualGearShifter  
ConcreteImplementor: AutoGearShifter 

主题演讲:

  1. 现在车辆换挡杆可以独立更改。

  2. 如果车辆更改,只需要更改汽车卡车

  3. 如果换挡杆更改,只需要更改手动换挡杆自动换挡杆

  4. 由于车辆(抽象)通过组合包含换挡杆(实现),因此换挡杆的更改不会影响车辆

  5. 由于换挡杆(实现者)不包含或引用车辆(抽象),因此抽象的更改不会影响实现。

编辑:

桥接模式呈现两个正交类层次结构 - 一个用于抽象,另一个用于实现,可以独立更改而不依赖于其他内容。


独立意味着接口本身或实现方式可以改变,还是只有具体类可以改变?如果只有类发生变化,那么我认为策略模式中的策略也可以独立地进行更改,不是吗? - Matthias Müller
1
实现者接口可以更改,如果接口更改,则具体实现也会更改。但与此同时,其他类层次结构(如抽象和重新定义的抽象)无需更改。现在,抽象和重新定义的抽象可以更改,但实现者接口和具体实现者不需要更改。只需考虑两个正交的类层次结构可以独立更改。策略模式只有一个层次结构,但桥接模式有两个不同的层次结构。 - Ravindra babu
非常感谢,我认为最后一句话是重要的区别,据我所知。也许你可以在编辑中添加这个。 - Matthias Müller

1
在策略模式中,"父类"在特定操作中的活动是不变的,而"子类"的活动可以变化。然而,在桥接模式中,"父类"和"子类"的活动都可以变化。
例如,
public class Ticket {
    
    Date dateOfTravel;
    int distance;
    Vehicle vehicle;
    Seat  seat;
    
    public float getTotalFare(){
         //depends on 
               //Distance
               //Vehicle - whether Vehicle is AC or non-AC. 
               //Seat - based on the location of the Seat.
     
        //Fare = vehicleBaseFare*seatMultiplier*distance

    }
    
}

在上述情况中,变化取决于父级(距离)以及子级(车辆和座位)。因此,车辆和座位都起到了桥梁的作用。现在,在这里。
public class Vehicle {

   TrackingDevice device;

   public Coordinates getCoordinates(){
       return device.getCoordinates();
   }
}

这里,父类的作用是恒定的,也就是没有作用!因此,这是一种策略模式。

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