1. 概念
- 桥接模式是一种结构型设计模式,它的主要目的是将抽象化与实现化分离,使得它们可以独立变化。
2. 原理结构图
- 原理结构图说明
- 抽象(Abstraction)角色:定义抽象,它包含对实现角色的引用。这个角色是桥接模式的核心,它提供了一个接口,用于操作实现角色。
- 扩展抽象(Refined Abstraction)角色:这是抽象的扩展,可以是抽象类的子类或具体实现类。它进一步细化了抽象角色的行为和功能。
- 实现(Implementor)角色:定义实现接口,这个接口通常由具体实现角色来实现。实现角色为抽象角色提供了具体的实现方式。
- 具体实现(Concrete Implementor)角色:实现实现角色接口的具体类。这些类提供了具体的实现细节,使得抽象可以独立于实现变化。
3. 代码示例
3.1 示例1
- 正在设计一个图形编辑系统,其中形状(Shape)和颜色(Color)是两个可以独立变化的维度。
// Color 接口定义了一个颜色
interface Color {
void fill();
}
// Red 类实现了 Color 接口,代表红色
class Red implements Color {
@Override
public void fill() {
System.out.println("Inside Red::fill() method.");
}
}
// Green 类实现了 Color 接口,代表绿色
class Green implements Color {
@Override
public void fill() {
System.out.println("Inside Green::fill() method.");
}
}
// Shape 是一个抽象类,它有一个 Color 类型的引用
abstract class Shape {
protected Color color;
public Shape(Color color) {
this.color = color;
}
public abstract void draw();
}
// Circle 类继承了 Shape 类,代表圆形
class Circle extends Shape {
public Circle(Color color) {
super(color);
}
@Override
public void draw() {
System.out.println("Inside Circle::draw() method.");
color.fill();
}
}
// Rectangle 类继承了 Shape 类,代表矩形
class Rectangle extends Shape {
public Rectangle(Color color) {
super(color);
}
@Override
public void draw() {
System.out.println("Inside Rectangle::draw() method.");
color.fill();
}
}
public class BridgePatternDemo {
public static void main(String[] args) {
Shape redCircle = new Circle(new Red());
Shape greenRectangle = new Rectangle(new Green());
redCircle.draw();
greenRectangle.draw();
}
}
- 运行BridgePatternDemo的main方法时,输出:
Inside Circle::draw() method.
Inside Red::fill() method.
Inside Rectangle::draw() method.
Inside Green::fill() method.
- Color是抽象部分,而Red和Green是它的具体实现。Shape是抽象部分的使用者,而Circle和Rectangle是它的具体实现。客户端代码通过桥接模式将Color的实现与Shape的实现结合起来。这使得我们可以独立地扩展颜色或形状,而不需要修改现有的代码。
3.2 示例2
- 有一个遥控器接口,以及多个设备实现类。遥控器可以通过桥接接口来控制这些设备。
// 设备接口
interface Device {
void turnOn();
void turnOff();
void setVolume(int volume);
int getVolume();
}
// 电视实现类
class TV implements Device {
private int volume;
@Override
public void turnOn() {
System.out.println("TV is turned on.");
}
@Override
public void turnOff() {
System.out.println("TV is turned off.");
}
@Override
public void setVolume(int volume) {
this.volume = volume;
System.out.println("TV volume set to " + volume);
}
@Override
public int getVolume() {
return volume;
}
}
// 空调实现类
class AirConditioner implements Device {
private boolean isOn;
@Override
public void turnOn() {
isOn = true;
System.out.println("Air conditioner is turned on.");
}
@Override
public void turnOff() {
isOn = false;
System.out.println("Air conditioner is turned off.");
}
@Override
public void setVolume(int volume) {
// 空调没有音量设置,这里可以抛出异常或忽略
}
@Override
public int getVolume() {
// 空调没有音量,可以返回默认值或抛出异常
return 0;
}
}
// 遥控器接口
interface RemoteControl {
void setDevice(Device device);
void turnOn();
void turnOff();
void setVolume(int volume);
}
// 具体的遥控器实现类
class SimpleRemoteControl implements RemoteControl {
private Device device;
@Override
public void setDevice(Device device) {
this.device = device;
}
@Override
public void turnOn() {
if (device != null) {
device.turnOn();
} else {
System.out.println("No device is set for the remote control.");
}
}
@Override
public void turnOff() {
if (device != null) {
device.turnOff();
} else {
System.out.println("No device is set for the remote control.");
}
}
@Override
public void setVolume(int volume) {
if (device != null) {
device.setVolume(volume);
} else {
System.out.println("No device is set for the remote control.");
}
}
}
public class BridgePatternDemo {
public static void main(String[] args) {
// 创建设备对象
Device tv = new TV();
Device ac = new AirConditioner();
// 创建遥控器对象
RemoteControl remoteControl = new SimpleRemoteControl();
// 将设备设置到遥控器中
remoteControl.setDevice(tv);
// 使用遥控器控制设备
remoteControl.turnOn(); // 输出: TV is turned on.
remoteControl.setVolume(10); // 输出: TV volume set to 10
// 更换设备
remoteControl.setDevice(ac);
// 再次使用遥控器控制设备
remoteControl.turnOn(); // 输出: Air conditioner is turned on.
// remoteControl.setVolume(10); // 空调没有音量设置,可以选择忽略或抛出异常
remoteControl.turnOff(); // 输出: Air conditioner is turned off.
}
}
- 运行BridgePatternDemo的main方法时,输出:
TV is turned on.
TV volume set to 10
Air conditioner is turned on.
Air conditioner is turned off.
- 运行上面的代码,你会看到遥控器可以成功地控制电视(包括调节音量)和空调(仅控制开关)。桥接模式使得我们可以将遥控器与它所控制的设备解耦,允许我们为遥控器和设备添加更复杂的功能或更多的设备类型,而不需要修改遥控器的代码。这展示了桥接模式的灵活性和可扩展性。
- 例如,如果我们想为遥控器添加更多的控制功能,如静音或调节亮度,我们只需要在Device接口中添加新的方法,并在各个设备实现类中实现这些方法。然后,我们需要在RemoteControl接口和SimpleRemoteControl类中添加相应的控制方法。
- 此外,我们还可以创建更多的设备实现类,例如投影仪、音响等,只需要它们实现Device接口,并在遥控器中设置相应的设备对象,就可以通过遥控器来控制这些新设备了。
4. 优缺点
- 主要作用
- 将抽象部分与实现部分分离,降低系统的耦合度,提高扩展性和可维护性。
- 优点
- 分离抽象和实现:桥接模式通过将抽象与其实现分离,使得它们可以独立变化。这有助于降低系统各部分之间的耦合度,从而提高了模块的独立性。
- 提高可扩展性:此模式能够在多个维度上进行扩展,这意味着系统可以更容易地应对未来可能的变化,例如新增功能或修改现有功能。
- 取代多层继承方案:在面向对象设计中,过多的继承会导致系统复杂难以维护。桥接模式提供了一种替代方案,它减少了子类的数目并遵循单一职责原则,增强了代码的复用性。
- 实现细节对客户透明:由于接口与实现分离,客户只需要知道抽象接口和实现接口,而不需要关心具体的实现细节。这有助于降低系统的复杂性,提高系统的可理解性和可维护性。
- 缺点
- 增加系统理解与设计难度:由于桥接模式中的聚合关联关系建立在抽象层,因此要求开发者必须针对抽象进行设计和编程,这可能会增加系统的理解和设计的难度。
- 需要正确识别独立变化的维度:使用桥接模式需要准确地识别出系统中两个独立变化的维度。若识别不当,则可能导致设计上的失误,限制了模式的有效应用范围。
- 可能增加代码复杂性:由于抽象和实现被分离,可能会导致代码的整体复杂性增加,特别是在涉及到多个实现和抽象组合时。
5. 应用场景
- 游戏开发:在游戏开发中,角色和装备的属性、技能等经常需要根据游戏设计的需要进行扩展和调整。使用桥接模式,可以将角色的抽象表示(如角色类型、等级等)与具体的属性、技能实现分离开来。这样,可以动态地组合和替换不同的属性和技能,实现游戏的多样化和个性化,同时降低了系统之间的耦合度。
- 电子商务平台:在电子商务平台中,商品和订单的处理逻辑可能需要根据不同的业务规则或市场策略进行调整。通过桥接模式,可以将商品的抽象表示(如商品类型、价格等)与具体的业务逻辑实现(如促销规则、折扣计算等)分离开来。这样,可以灵活地添加或修改业务规则,而无需修改商品的核心逻辑,提高了系统的可扩展性和可维护性。
- 图形用户界面(GUI)开发:在开发图形用户界面时,不同的操作系统或平台可能有不同的窗口组件和绘制方式。使用桥接模式,可以将窗口组件的抽象部分(如按钮、文本框等)与具体的绘制和事件处理实现分离开来。这样,可以轻松地实现跨平台的窗口界面,并动态地组合和替换不同的组件和绘制方式,以适应不同的用户需求或平台特性。
- 插件式架构:桥接模式非常适合用于实现插件式架构。通过将插件的接口与具体实现分离,可以使得插件的加载、卸载和替换变得简单和灵活。这对于需要支持第三方扩展或自定义功能的系统来说非常有用。
6. JDK中的使用
JDBC(Java Database Connectivity)驱动程序模型。
JDBC API 提供了一套数据库操作的抽象接口,而具体的数据库连接和操作实现则是由各个数据库厂商提供的驱动程序来完成的。这样的设计允许应用程序使用统一的API来操作不同的数据库,而无需关心底层的具体实现。具体如下:
- 抽象部分:JDBC定义了一组接口,这些接口为数据库操作提供了抽象层。应用程序通过这些接口与数据库进行交互,而不需要直接与特定的数据库通信协议打交道。这组接口包括Connection、Statement、ResultSet等,它们构成了JDBC的抽象层。
- 实现部分:数据库供应商提供了实现JDBC接口的具体类,这些类知道如何与特定类型的数据库进行通信。例如,Oracle数据库的驱动会提供OracleConnection、OracleStatement等实现类,这些类实现了JDBC接口并包含了与Oracle数据库通信的具体逻辑。
- 桥接器:DriverManager类在JDBC中扮演了桥接器的角色。它负责管理一组驱动程序对象,并在应用程序请求数据库连接时,选择一个合适的驱动程序来建立连接。DriverManager不是抽象类或接口,但它使用了桥接模式的结构来分离抽象和实现。
7. 桥接模式 VS 适配器模式
- 适配器模式(两类之间的转换器):将两个不兼容的接口连起来(现有代码不兼容使用)
- 桥接模式:抽象和实现进行分离(架构上的设计),前期设计
8. 注意事项
- 接口的设计:桥接模式依赖于接口来连接抽象部分和实现部分。因此,接口的设计至关重要。接口应该足够通用,能够容纳多种可能的实现,同时又要足够具体,能够清晰地表达抽象部分的需求。
- 避免过度设计:虽然桥接模式提供了很大的灵活性,但过度使用也可能导致系统变得复杂和难以维护。因此,在决定是否使用桥接模式时,需要权衡其带来的好处和可能引入的复杂性。
- 系统的理解和设计难度:由于桥接模式增加了系统的抽象层次,要求开发者针对抽象进行设计和编程,这可能会增加系统的理解和设计难度。因此,在使用桥接模式时,需要确保团队成员对模式有深入的理解,并能够正确地应用它。
- 识别独立变化的维度:桥接模式要求正确识别出系统中两个独立变化的维度(抽象、实现)。这需要对系统的需求和结构有深入的理解,以便确定哪些部分应该被视为抽象,哪些部分应该被视为实现。
- 兼容性问题:在使用桥接模式时,特别是在进行跨平台或跨语言集成时,需要注意不同组件或系统之间的兼容性问题。确保所选的抽象和实现组件能够无缝地协同工作。
- 性能考虑:桥接模式可能会引入额外的间接层,这可能会对系统的性能产生一定的影响。因此,在设计系统时,需要综合考虑性能需求和桥接模式带来的好处。