【深入理解设计模式】桥接设计模式
桥接设计模式
桥接设计模式是一种结构型设计模式,它旨在将抽象部分与实现部分分离,使它们可以独立变化,从而更好地管理复杂性。桥接模式通常涉及多个层次的抽象,其中一个层次(通常称为"抽象")依赖于另一个层次(通常称为"实现")的实例。
定义:
将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
结构:
- 抽象化(Abstraction)角色 :定义抽象类,并包含一个对实现化对象的引用。
- 扩展抽象化(Refined Abstraction)角色 :是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
- 实现化(Implementor)角色 :定义实现化角色的接口,供扩展抽象化角色调用,它可以是一个接口或一个抽象类。实现并不一定要与抽象完全匹配,但必须提供抽象所需的方法。
- 具体实现化(Concrete Implementor)角色 :给出实现化角色接口的具体实现。
案例一:
// 实现部分接口 interface DrawingAPI { void drawCircle(double x, double y, double radius); } // 具体实现部分A class DrawingAPI1 implements DrawingAPI { @Override public void drawCircle(double x, double y, double radius) { System.out.printf("API1.drawCircle at %f:%f radius %f%n", x, y, radius); } } // 具体实现部分B class DrawingAPI2 implements DrawingAPI { @Override public void drawCircle(double x, double y, double radius) { System.out.printf("API2.drawCircle at %f:%f radius %f%n", x, y, radius); } } // 抽象部分 abstract class Shape { protected DrawingAPI drawingAPI; protected Shape(DrawingAPI drawingAPI) { this.drawingAPI = drawingAPI; } public abstract void draw(); } // 扩充抽象部分 class Circle extends Shape { private double x, y, radius; public Circle(double x, double y, double radius, DrawingAPI drawingAPI) { super(drawingAPI); this.x = x; this.y = y; this.radius = radius; } @Override public void draw() { drawingAPI.drawCircle(x, y, radius); } } public class BridgePatternExample { public static void main(String[] args) { // 使用实现部分A来画一个圆 Shape circle = new Circle(1, 2, 3, new DrawingAPI1()); circle.draw(); // 使用实现部分B来画一个圆 Shape anotherCircle = new Circle(5, 7, 11, new DrawingAPI2()); anotherCircle.draw(); } }
在上面的示例中,DrawingAPI 接口代表了实现部分,DrawingAPI1 和 DrawingAPI2 分别是具体的实现部分。Shape 是抽象部分,Circle 是扩充抽象部分。在 Circle 类中,通过 drawingAPI 调用实现部分的方法来绘制圆。在 BridgePatternExample 类的 main 方法中,我们可以看到如何使用不同的实现部分来绘制不同的圆。
案例二:
【例】视频播放器
需要开发一个跨平台视频播放器,可以在不同操作系统平台(如Windows、Mac、Linux等)上播放多种格式的视频文件,常见的视频格式包括RMVB、AVI、WMV等。该播放器包含了两个维度,适合使用桥接模式。
类图如下:
/** * @author OldGj 2024/02/26 * @version v1.0 * @apiNote 视频文件 - 抽象实现化角色 */ public interface VideoFile { public void decode(String fileName); }
/** * @author OldGj 2024/02/26 * @version v1.0 * @apiNote RMVB类型的视频文件 - 具体实现化角色 */ public class RMVBFile implements VideoFile { @Override public void decode(String fileName) { System.out.println("rmvb: " + fileName); } }
/** * @author OldGj 2024/02/26 * @version v1.0 * @apiNote Avi格式的视频文件 - 具体实现化角色 */ public class AVIFile implements VideoFile{ @Override public void decode(String fileName) { System.out.println("avi : "+fileName); } }
/** * @author OldGj 2024/02/26 * @version v1.0 * @apiNote 操作系统类 - 抽象化角色 */ public abstract class OperatingSystemVersion { //维护一个对实现的引用 protected VideoFile videoFile; public OperatingSystemVersion(VideoFile videoFile) { this.videoFile = videoFile; } public abstract void play(String fileName); }
/** * @author OldGj 2024/02/26 * @version v1.0 * @apiNote MAC操作系统 - 扩展抽象化角色 */ public class Mac extends OperatingSystemVersion{ public Mac(VideoFile videoFile) { super(videoFile); } @Override public void play(String fileName) { System.out.println("mac"); videoFile.decode(fileName); } }
/** * @author OldGj 2024/02/26 * @version v1.0 * @apiNote window操作系统 - 扩展抽象化角色 */ public class Window extends OperatingSystemVersion{ public Window(VideoFile videoFile) { super(videoFile); } @Override public void play(String fileName) { System.out.println("window"); videoFile.decode(fileName); } }
/** * @author OldGj 2024/02/26 * @version v1.0 * @apiNote 客户端 - 测试类 */ public class Client { public static void main(String[] args) { OperatingSystemVersion systemVersion = new Window(new RMVBFile()); systemVersion.play("战狼"); } }
桥接模式是如何进行分离抽象和实现的?
假设我们要设计一个画图应用程序,它可以在不同的平台上绘制不同的形状,比如在 Windows 平台上绘制矩形和圆形,在 Linux 平台上绘制矩形和椭圆形。
首先,我们定义两个抽象类:Shape 和 DrawingAPI。
// 抽象类 Shape abstract class Shape { protected DrawingAPI drawingAPI; protected Shape(DrawingAPI drawingAPI) { this.drawingAPI = drawingAPI; } public abstract void draw(); // 抽象方法,绘制形状 } // 抽象类 DrawingAPI interface DrawingAPI { void drawShape(); // 抽象方法,绘制形状 }
然后,我们定义具体的形状类,比如矩形和圆形,并且实现了 Shape 抽象类。
// 具体形状类 Rectangle class Rectangle extends Shape { public Rectangle(DrawingAPI drawingAPI) { super(drawingAPI); } @Override public void draw() { drawingAPI.drawShape(); // 调用实现部分的绘制方法 } } // 具体形状类 Circle class Circle extends Shape { public Circle(DrawingAPI drawingAPI) { super(drawingAPI); } @Override public void draw() { drawingAPI.drawShape(); // 调用实现部分的绘制方法 } }
接下来,我们实现两个不同的绘制API,比如在 Windows 平台和 Linux 平台上的绘制。
// 具体实现部分 DrawingAPI for Windows class DrawingAPIWindows implements DrawingAPI { @Override public void drawShape() { System.out.println("Drawing shape on Windows platform"); } } // 具体实现部分 DrawingAPI for Linux class DrawingAPILinux implements DrawingAPI { @Override public void drawShape() { System.out.println("Drawing shape on Linux platform"); } }
最后,我们在客户端代码中使用桥接模式来创建对象并调用方法。
public class BridgePatternExample { public static void main(String[] args) { // 在 Windows 平台上绘制矩形 Shape rectangleWindows = new Rectangle(new DrawingAPIWindows()); rectangleWindows.draw(); // 在 Linux 平台上绘制圆形 Shape circleLinux = new Circle(new DrawingAPILinux()); circleLinux.draw(); } }
在这个例子中,抽象部分是 Shape 抽象类,具体形状类 Rectangle 和 Circle 是扩充抽象部分。实现部分是 DrawingAPI 接口,具体实现部分 DrawingAPIWindows 和 DrawingAPILinux 分别是不同平台上的具体实现。
通过使用桥接模式,我们成功地将抽象部分和实现部分分离开来,使它们可以独立变化。例如,如果要在 Windows 平台上绘制其他形状,只需创建一个新的具体形状类,并提供相应的 DrawingAPIWindows 实现即可,而不需要修改现有的代码。这种设计使得系统更加灵活、可扩展和易于维护。
桥接模式的优点包括:
- 分离抽象和实现,使得它们可以独立变化,降低了系统的耦合度。
- 提高了系统的可扩展性,可以方便地增加新的抽象和实现。
- 提高了系统的可维护性,通过分离抽象和实现,减少了修改一个层次对另一个层次的影响。
使用场景
- 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
- 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
- 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。