继承是面向对象的三大特性之一,但很多时候使用继承的结果却不尽如人意。除了人尽皆知的紧耦合问题外,有的时候还会导致子类的快速膨胀。
设想这样一个场景:最初设计的时候有一个类型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#的工程化实现及扩展》
联系客服