设计模式(7) 桥接模式
- 桥接模式的概念与实现
- 为什么叫桥接模式
- 桥接模式的适用场景
继承是面向对象的三大特性之一,但很多时候使用继承的结果却不尽如人意。除了人尽皆知的紧耦合问题外,有的时候还会导致子类的快速膨胀。
设想这样一个场景:最初设计的时候有一个类型Product,但后来随着新需求的出现,X原因导致了它的变化,X有两种情况,则通过继承需要创建两个新的子类ProductX1,ProductX2,但后来有出现了Y因素也会导致Product的变化,如果Y有三种情况,则会出现ProductX1Y1,ProductX1Y2,ProductX1Y3...等,一共2*3=6个类。
使用这种继承的方式,如果再出现新的变化因素,或者某个变化因素出现了新的情况,都会导致子类的快速膨胀,给维护带来很大的挑战。
造成这个问题的根本原因是类型在沿着多个维度变化。为了应对变化,一般会通过抽象的方法,找到其中比较稳定的部分,然后抽象其行为,令客户程序依赖于抽象而不是具体实现。同样的道理,当一个类型同时受到多个因素变化的影响时,也通过把每个因素抽象,让类型依赖于一系列抽象因素的办法尽量处理这个问题,这便是桥接模式解决问题的思路。
桥接模式的概念与实现
GOF对桥接模式的描述为:
Decouple an abstraction from its implementationso that the two can vary independently.
— Design Patterns : Elements of Reusable Object-Oriented Software
桥接模式将抽象部分与它的实现部分分离,使它们都可以独立地变化。
桥接模式的UML类图为
示例代码:
public interface IImpl
{
void OperationImpl();
}
public interface IAbstraction
{
IImpl Implementor { get; set; }
void Operation();
}
public class ConcreteImplementatorA : IImpl
{
public void OperationImpl()
{
...
}
}
public class ConcreteImplementatorB : IImpl
{
public void OperationImpl()
{
...
}
}
public class RefinedAbstration : IAbstraction
{
public IImpl Implementor { get; set; }
public void Operation()
{
Implementor.OperationImpl();
}
}
这样子看起来还是比较抽象,再举个具体的例子汽车-道路,目前汽车有小汽车、巴士两类,路有水泥路、石子路两类,这样“车在路上行驶”就会有四种情况,这个场景用桥接模式来描述的话可以是:
汽车类的抽象与实现:
public interface IVehicle
{
string Drive();
}
public class Car : IVehicle
{
public string Drive()
{
return "Car";
}
}
public class Bus : IVehicle
{
public string Drive()
{
return "Bus";
}
}
通过桥接模式关联道路与汽车:
public abstract class Road
{
protected IVehicle vehicle;
public Road(IVehicle vehicle)
{
this.vehicle = vehicle;
}
public abstract string DriveOnRoad();
}
public class UnpavedRoad : Road
{
public UnpavedRoad(IVehicle vehicle) : base(vehicle) { }
public override string DriveOnRoad()
{
return vehicle.Drive() + " is on Unpaved Road";
}
}
public class CementRoad : Road
{
public CementRoad(IVehicle vehicle) : base(vehicle) { }
public override string DriveOnRoad()
{
return vehicle.Drive() + " is on Cement Road";
}
}
调用:
IVehicle vehicle = new Car();
Road road = new CementRoad(vehicle);
Console.WriteLine(road.DriveOnRoad());
// Car is on Cement Road
Speed speed = new FastSpeed(road);
Console.WriteLine(speed.DriveWithSpeed());
// Car is on Cement Road,
在这里Road依赖的是IVehivle抽象,具体的汽车实现在调用的时候决定。
对比直接继承出四种类型的方式,这样做的好处貌似并不明显,还是需要四个类,而且更复杂,但如果汽车或者道路类型继续增加,或者引入了别的变化因素,情况就不一样了。
为什么叫桥接模式
有个疑惑是关于桥接模式的名称的,为什么叫桥接模式呢?之前的工厂、适配器等名称都挺形象的,但桥接模式好像有点不同,这要从桥接模式解决问题的思路说起,桥接模式更多的是提示我们面向对象的设计分解方式,可以概括为三步:
- 第一步,把依赖具体变成依赖抽象。
- 第二步,如果对象同时沿着多个维度变化,那就顺次展开抽象因素。
- 第三步,为每个抽象因素提供具体实现。
示意图:
到了第三步,就可以大概看出桥接模式的形象化表示了,IX、IY和IZ构成连接部(被称为支座),而每个具体类形成了一个个桥墩。
所以桥接模式,是以抽象之间的依赖为桥面、以具体实现为桥墩,建起了一座连接需求与实现的桥梁。
桥接模式的适用场景
- 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的联系。
- 设计要求实现化角色的任何改变不应当影响客户端,或者说实现化角色的改变对客户端是完全透明的。
- 一个构件有多于一个的抽象化角色和实现化角色,系统需要它们之间进行动态耦合。
- 虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。
参考书籍:
王翔著 《设计模式——基于C#的工程化实现及扩展》