22. 桥接模式
约 1305 字大约 4 分钟
2026-03-24
合成/聚合复用原则
合成/聚合复用原则(CARP),尽量使用合成/聚合,尽量不要使用类继承。
合成(Composition,也有翻译成组合)和聚合(Aggregation)都是关联的特殊种类。聚合表示一种弱的‘拥有’关系,体现的是A对象可以包含B对象,但B对象不是A对象的一部分:合成则是一种强的‘拥有’关系,体现了严格的部分和整体的关系,部分和整体的生命周期一样。
比方说,大雁有两个翅膀,翅膀与大雁是部分和整体的关系,并且它们的生命周期是相同的,于是大雁和翅膀就是合成关系。而大雁是群居动物,所以每只大雁都是属于一个雁群,一个雁群可以有多只大雁,所以大雁和雁群是聚合关系。
合成/聚合复用原则的好处是,优先使用对象的合成/聚合将有助于你保持每个类被封装,并被集中在单个任务上。这样类和类继承层次会保持较小规模,并且不太可能增长为不可控制的庞然大物。
很多情况用继承会带来麻烦。比如,对象的继承关系是在编译时就定义好了,所以无法在运行时改变从父类继承的实现。子类的实现与它的父类有非常紧密的依赖关系,以至于父类实现中的任何变化必然会导致子类发生变化。当你需要复用子类时,如果继承下来的实现不适合解决新的问题,则父类必须重写或被其他更适合的类替换。这种依赖关系限制了灵活性并最终限制了复用性。
桥接模式定义
桥接模式(Bridge),将抽象部分与它的实现部分分离,使它们都可以独立地变化。
人话:桥接模式就像手机的“品牌”和“软件”之间的关系。比如有不同品牌的手机(华为、小米、苹果等),同时又有各种软件(游戏、聊天软件、音乐软件等)。
如果不用桥接模式来设计,可能会有两种很麻烦的方式: 一种是按“手机品牌”来设计,每个品牌手机里都写死各种软件功能,那么一旦新增一个软件,就需要给每一个手机品牌都加一遍; 另一种是按“软件”来设计,每个软件都要适配所有手机品牌,那么一旦新增一个手机品牌,就需要修改所有软件。这样就会导致类的数量爆炸,而且修改成本非常高,这在软件中就属于维度耦合过高、扩展困难。
而使用桥接模式之后,就相当于在“手机品牌”和“软件”之间引入了一个“桥”(类似一个统一的操作系统接口)。手机只负责“是什么品牌”,软件只负责“做什么功能”,两者通过这个桥进行连接。
这样一来,手机品牌可以独立扩展(新增一个品牌不需要修改所有软件),软件也可以独立扩展(新增一个软件不需要修改所有手机)。客户端只需要把“某个手机品牌”和“某个软件”组合起来使用即可。
在软件中也是一样:桥接模式把“抽象部分”和“实现部分”分离开来,让它们可以各自独立变化。就像手机品牌和软件解耦一样,避免了类的爆炸式增长,让系统更加灵活、可扩展。
桥接模式(Bridge)结构图
Abstraction类——抽象部分:
// Abstraction(抽象类)
class Abstraction
{
protected Implementor implementor;
public void SetImplementor(Implementor implementor)
{
this.implementor = implementor;
}
public virtual void Operation()
{
implementor.Operation();
}
}RefinedAbstraction类——扩展抽象:
// RefinedAbstraction(扩展抽象)
class RefinedAbstraction : Abstraction
{
public override void Operation()
{
implementor.Operation();
}
}Implementor类——实现部分接口:
// Implementor(实现接口)
abstract class Implementor
{
public abstract void Operation();
}ConcreteImplementor类——具体实现:
// ConcreteImplementorA
class ConcreteImplementorA : Implementor
{
public override void Operation()
{
Console.WriteLine("具体实现 A 的方法执行");
}
}
// ConcreteImplementorB
class ConcreteImplementorB : Implementor
{
public override void Operation()
{
Console.WriteLine("具体实现 B 的方法执行");
}
}客户端代码:
static void Main(string[] args)
{
Abstraction ab = new RefinedAbstraction();
// 使用实现 A
ab.SetImplementor(new ConcreteImplementorA());
ab.Operation();
// 切换为实现 B
ab.SetImplementor(new ConcreteImplementorB());
ab.Operation();
Console.Read();
}- 实现系统可能有多角度分类,每一种分类都有可能变化,那么就把这种多角度分离出来让它们独立变化,减少它们之间的耦合。也就是说,在发现我们需要多角度去分类实现对象,而只用继承会造成大量的类增加,不能满足开放-封闭原则时,就应该要考虑用桥接模式了。
