- 作者:老汪软件技巧
- 发表时间:2024-08-24 04:01
- 浏览量:
桥接模式的原理非常简单,但是使用起来会有一定的难度,所以相对于适配器模式来说,在理解桥接模式时,重点要能跳出局部,多从整体结构上去思考。
一、模式原理分析
桥接模式的定义是:将抽象部分与它的实现部分分离,使它们都可以独立地变化。
不过,这里的抽象常常容易被理解为抽象类,并将实现理解为继承后的“派生类”,但是这样理解存在局限性,因为 GoF 的本意是想表达“从对象与对象间的关系去看,做抽象实体与抽象行为的分离”,所以使用抽象实体和抽象行为来描述更为准确。
我们来看看桥接模式的 UML 描述,如下图所示:
从该图中,我们可以看到桥接模式主要包含了以下四个关键角色。
桥接模式原理的核心是抽象与抽象之间的分离,这样分离的好处就在于,具体的实现类依赖抽象而不是依赖具体,满足 DIP 原则,很好地完成了对象结构间的解耦。换句话说,抽象的分离间接完成了具体类与具体类之间的解耦,它们之间使用抽象来进行组合或聚合,而不再使用继承。
下面我们再来看看桥接模式对应 UML 图的代码实现,具体如下:
public abstract class AbstractEntity {
//行为对象
protected AbstractBehavior myBehavior;
//实体与行为的关联
public AbstractEntity(AbstractBehavior aBehavior) {
myBehavior = aBehavior;
}
//子类需要实现的方法
public abstract void request();
}
public class DetailEntityA extends AbstractEntity {
public DetailEntityA(AbstractBehavior aBehavior) {
super(aBehavior);
}
@Override
public void request() {
super.myBehavior.operation1();
}
}
public class DetailEntityB extends AbstractEntity {
public DetailEntityB(AbstractBehavior aBehavior) {
super(aBehavior);
}
@Override
public void request() {
super.myBehavior.operation2();
}
}
public abstract class AbstractBehavior {
public abstract void operation1();
public abstract void operation2();
}
public class DetailBehaviorA extends AbstractBehavior{
@Override
public void operation1() {
System.out.println("op-1 from DetailBehaviorA");
}
@Override
public void operation2() {
System.out.println("op-2 from DetailBehaviorA");
}
}
public class DetailBehaviorB extends AbstractBehavior {
@Override
public void operation1() {
System.out.println("op-1 from DetailBehaviorB");
}
@Override
public void operation2() {
System.out.println("op-2 from DetailBehaviorB");
}
}
从上面的代码实现你会很容易发现,桥接模式封装了如下变化:
桥接模式封装变化的本质上是对事物进行分类(实体),并对实体中的功能性(行为)再划分的一种解决方案。比如,电子产品可以被分为手机、电脑等,其中手机隐藏了手机一类相关的变化;同样,手机和电脑都具备使用 App 软件的功能,它们各自隐藏了如何使用 App 的具体方式。在面向对象软件开发中,我们通常是使用接口或抽象类来作为抽象实体和具体实体,使用具体对象实例和实现接口的对象作为抽象行为和具体行为。
所以说,桥接模式的本质是通过对一个对象进行实体与行为的分离,来将需要使用多层继承的场景转换为使用组合或聚合的方式,进而解耦对象间的强耦合关系,达到对象与对象之间的动态绑定的效果,提升代码结构的扩展性。
二、使用场景分析
一般来讲,桥接模式的常用场景有如下几种。
接下来,我们通过一个不同操作系统下的文件上传例子来快速理解桥接模式的使用场景。
我们首先创建一个抽象实体类 FileUploader,它包含了两个抽象行为:上传(upload)和检查(check)。
public interface FileUploader {
Object upload(String path);
boolean check(Object object);
}
我们再建立一个具体实体类 FileUploaderImpl,其中包含了抽象行为类 FileUploadExcutor(文件上传执行器),实现了抽象行为 upload 和 check。
public class FileUploaderImpl implements FileUploader {
private FileUploadExcutor excutor = null;
public FileUploaderImpl(FileUploadExcutor excutor) {
this.excutor = excutor;
}
@Override
public Object upload(String path) {
return excutor.uploadFile(path);
}
@Override
public boolean check(Object object) {
return excutor.checkFile(object);
}
}
public interface FileUploadExcutor {
Object uploadFile(String path);
boolean checkFile(Object object);
}
接下来,在 Linux 平台上实现文件上传执行器 LinuxFileUpLoadExcutor,在 Windows 上实现文件上传执行器 WindowsFileUpLoadExcutor,具体代码如下所示:
public class LinuxFileUpLoadExcutor implements FileUploadExcutor {
@Override
public Object uploadFile(String path) {
return null;
}
@Override
public boolean checkFile(Object object) {
return false;
}
}
public class WindowsFileUpLoadExcutor implements FileUploadExcutor {
@Override
public Object uploadFile(String path) {
return null;
}
@Override
public boolean checkFile(Object object) {
return false;
}
}
从上面的代码可以发现:通过将文件上传执行器和文件上传行为进行分离,就能实现实体和行为的灵活演化。比如,当你想要实现一个新的上传到云存储的文件上传执行器时,你可以先新建一个叫 OSSFileUploaderImpl 的具体实现类,然后建立对应的云存储文件执行器,接着再分别实现华为云、阿里云、腾讯云等各种不同云存储的文件上传执行器。如果你还想要在执行器里加入新的行为,比如删除,这时平台上的执行器并不需要调用“删除”这个接口,这样就做到了实体和行为的解耦,极大地提升了代码的扩展性。
三、为什么要使用桥接模式?
第一个,为了灵活扩展代码结构。 上面使用了适配器模式和门面模式的桥接模式就是一个很好的思考方向,与通过硬编码直接调用 API 的形式相比,“通过模式来扩展”会更容易控制代码行数和逻辑结构。而从我多年的工作经验来看,在很多大规模的代码系统中,有结构的代码的可维护性会更好。因为是人来维护代码的,而人的特性是天生对结构型的东西更“敏感”,并且灵活的结构在后期进行代码重构时也能更好地替换与修改。
第二个,为了更好地解决跨平台兼容性问题。 桥接模式之所以能很好地解决跨平台的兼容性问题,就是因为桥接模式通过抽象层次上结构的分离,让相关的分类能够聚合到各自相关的层次逻辑中,而不同的平台对于同一个 API 在具体的代码实现上是不同的,这样反而符合不同操作系统按照各自维度演化的特性。
第三个,为了在运行时组合不同的组件。 无论是框架还是外部服务,我们都需要基于一个统一的协议进行协同工作,但是通过静态的继承方法很难做到在程序运行时进行方法或组件的动态更换。而使用桥接模式和门面模式就可以很方便地进行替换,比如,在上面文件上传执行器的案例中,我们可以使用一个统一的 API 网关调用不同的云服务来完成文件上传。
四、桥接模式的优缺点
使用桥接模式主要有以下四个大的优点。
同样,桥接模式也有一些缺点。
桥接模式可以说是 DIP 原则的具体实践。在软件开发中,一个对象可以从实体和行为两个角度来进行分离,其实就是将依赖从一个大而全的对象变换到依赖两个可以独立变化的维度,控制也就发生了反转。
桥接模式因为重视组合和聚合,从而有效避免了多重继承带来的问题。也就是说,通过抽象实体与抽象行为的关联,将静态的继承关系转换为了动态的组合关系,从而使得系统结构更加灵活。