08-开关与电灯:桥接模式
8.1开关与电灯
本章背景故事是生活中常用的开关与电灯。
8.2 模式定义
桥接模式(Bridge Pattern),也称为桥梁模式。在软件系统中,某些类型由于自身的逻辑,具有两个或多个维度的变化,如何应对这种“多维度的变化”?桥接模式使得软件系统能够轻松地沿着多个方向进行变化,而又不引入额外的复杂度。
桥接模式的用意是“将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化”。这句话有三个关键词,即抽象化、实现化和脱耦。
1)抽象化
存在于多个实体中的共同的概念性联系,就是抽象化。作为一个过程,抽象化就是忽略一些信息,从而把不同的实体当作同样的实体对待。
2)实现化
抽象化给出的具体实现,就是实现化。
3)脱耦
所谓耦合,就是两个实体的行为的某种强关联。而将它们的强关联去掉,就是耦合的解脱,或称为脱耦。在这里,脱耦是指将抽象化和实现化之间的耦合解脱开来,或者说是将它们之间的强关联改为弱关联。
将两个角色之间的继承关系改为聚合关系,就是将它们之间的强关联改为弱关联。因此,桥接模式中的脱耦,就是指在一个软件系统的抽象化和实现化之间使用组合/聚合关系而不是继承关系,从而使两者可以相对独立地变化。这就是桥接模式的用意所在。
8.3 一般化分析
本章我们来介绍开关和电灯的例子,看看开关和电灯之间到底如何运作才是最理想的。
在示例中出现了以下几个角色:
1)开关
2)电灯
3)电线
我们先来分析一般情况下,该如何实现。我们知道,开关控制电灯,开关接通电源,电灯亮;开关断开,电灯熄灭。这样看来,开关应该是一个基类,类中含有开灯、关灯、照明等抽象方法,还要有一个开灯照明的方法提供给外部应用调用;然后,电灯继承开关基类,具体实现开灯、关灯、照明等抽象方法,告诉外部应用现在使用的是哪盏灯。开关和电灯之间的关系如图所示:
8.4 一般化实现
8.4.1 建立抽象开关
新建抽象开关基类“AbstractSwitch”。该类中存在三个抽象方法:打开开关、照明和关闭开关,还有一个提供给外部应用调用的开灯照明方法。
package com.demo.common; /** * Created by Daniel on 2018/3/18. * 开关基类 */ public abstract class AbstractSwitch { //打开开关 public abstract void turnOn(); //照明 public abstract void light(); //关闭开关 public abstract void turnOff(); //开灯照明 public final void makeLight(){ this.turnOn(); this.light(); this.turnOff(); } }
8.4.2 电灯实现
1. 白炽灯实现——IncandescentLight
package com.demo.common.sub; import com.demo.common.AbstractSwitch; /** * Created by Daniel on 2018/3/18. * 白炽灯 */ public class IncandescentLight extends AbstractSwitch { //打开开关方法实现 @Override public void turnOn() { System.out.println("白炽灯打开了……"); } //照明方法实现 @Override public void light() { System.out.println("白炽灯照明!"); } //关闭开关方法实现 @Override public void turnOff() { System.out.println("白炽灯关闭了……"); } }
2. 水晶灯实现——CrystalLight
package com.demo.common.sub; import com.demo.common.AbstractSwitch; /** * Created by Daniel on 2018/3/18. * 水晶灯 */ public class CrystalLight extends AbstractSwitch { //打开开关方法实现 @Override public void turnOn() { System.out.println("水晶灯打开了……"); } //照明方法实现 @Override public void light() { System.out.println("水晶灯照明!"); } //关闭开关方法实现 @Override public void turnOff() { System.out.println("水晶灯关闭了……"); } /** * 使用遥控开关控制开灯 * @param operColor 灯颜色 */ public final void makeRemoteLight(int operColor){ //打开开关,接通电流 this.turnOn(); //照明 this.light(); String color = ""; switch (operColor){ case 1 : color = "暖色"; break; case 2 : color = "蓝色"; break; case 3 : color = "红色"; break; default: color = "白色"; break; } System.out.println("---现在是"+color+"!"); //关闭开关,断开电流 this.turnOff(); } }
8.4.3 客户端测试
import com.demo.common.AbstractSwitch; import com.demo.common.sub.CrystalLight; import com.demo.common.sub.IncandescentLight; /** * Created by Daniel on 2018/3/18. * 客户端应用程序 */ public class Client { public static void main(String[] args) { //白炽灯实例 AbstractSwitch light = new IncandescentLight(); //水晶灯实例 CrystalLight light2 = new CrystalLight(); //一般开关 System.out.println("---一般开关---"); light.makeLight(); //遥控开关 System.out.println("\n---遥控开关---"); light2.makeRemoteLight(1); } }
注意:要想使用水晶灯的遥控功能就必须创建水晶灯实例对象,不能使用超类类型,因为超类中没有遥控的功能。
运行客户端程序,结果如下图所示:
8.4.4 对于扩展功能的思考
已经实现了开灯、照明和关灯,是不是已经解决了所有的问题呢?答案是否定的。试想一下,我现在想用遥控开关遥控白炽灯,该怎么办呢?再创建一个子类?这样开销太大了。白炽灯已经存在,遥控开关也已经存在,为什么还要新建一个类呢?问题是,我们一开始的设计思路就是错误的。不应该使用继承,因为继承使开关和电灯关联得太强了,也就是一个开关控制一种灯,要想更换其他种类的电灯就必须配备相应的开关才行。更换电灯的同时还要更换开关,这是不应该出现的。
8.5 桥接模式分析方法
我们重新分析一下,其实,开关和电灯是完全分开的两个部分,是彼此独立的工作,不应该因为对方的变换而受到影响。所以,要使用组合,不能使用继承。重新建立关系图如下:
8.6 开关与电灯的桥接模式实现
8.6.1 创建电灯接口
package com.demo.bridge.lights; /** * Created by Daniel on 2018/3/18. * 电灯接口 */ public interface ILight { //接通电流 public void electricConnected(); //照明 public void light(); //断开电流 public void electricClosed(); }
8.6.2 创建开关
1. 创建一般开关——BaseSwitch
package com.demo.bridge.switchs; import com.demo.bridge.lights.ILight; /** * Created by Daniel on 2018/3/18. * 开关顶层类 */ public class BaseSwitch { //使用组合,设置ILight为内部私有属性,此为桥梁 protected ILight light; //构造方法将外部的light类型注入进来 public BaseSwitch(ILight light){ this.light = light; } /** * 开灯方法 */ public final void makeLight(){ //打开开关,接通电流 this.light.electricConnected(); //照明 this.light.light(); //关闭开关,断开电流 this.light.electricClosed(); } }
注意:将ILight电灯接口作为开关基类的属性使用,这就是聚合的方式。而开关基类的开灯照明方法,是委托给ILight电灯接口完成的。这是一般开关实现,下面看看遥控开关。
2. 创建遥控开关——RemoteControlSwitch
package com.demo.bridge.switchs; import com.demo.bridge.lights.ILight; /** * Created by Daniel on 2018/3/18. * 遥控开关,继承BaseSwitch扩展功能 */ public class RemoteControlSwitch extends BaseSwitch{ //构造方法 public RemoteControlSwitch(ILight light) { super(light); } /** * 使用遥控开关控制开灯 * @param operColor 灯颜色 */ public final void makeRemoteLight(int operColor){ //打开开关,接通电流 this.light.electricConnected(); //照明 this.light.light(); String color = ""; switch (operColor){ case 1: color = "暖色"; break; case 2: color = "蓝色"; break; case 3: color = "红色"; break; default: color = "白色"; break; } System.out.println("--现在是"+color+"!"); //关闭开关,断开电流 this.light.electricClosed(); } }
注意:遥控开关的方法中,只做了关于电灯颜色的控制,电灯开关控制仍是委托给ILight电灯接口的。
8.6.3 电灯实现
1. 白炽灯实现——IncandescentLight
package com.demo.bridge.lights.impl; import com.demo.bridge.lights.ILight; /** * Created by Daniel on 2018/3/18. * 白炽灯实现 */ public class IncandescentLight implements ILight{ //接通电流 public void electricConnected() { System.out.println("白炽灯被打开了……"); } //照明 public void light() { System.out.println("白炽灯照明!"); } //断开电流 public void electricClosed() { System.out.println("白炽灯被关闭了……"); } }
2. 水晶灯实现——CrystalLight
package com.demo.bridge.lights.impl; import com.demo.bridge.lights.ILight; /** * Created by Daniel on 2018/3/18. * 水晶灯实现 */ public class CrystalLight implements ILight { //接通电流 public void electricConnected() { System.out.println("水晶灯被打开了……"); } //照明 public void light() { System.out.println("水晶灯照明!"); } //断开电流 public void electricClosed() { System.out.println("水晶灯被关闭了……"); } }
8.6.4 客户端测试
1. 一般化测试
import com.demo.bridge.lights.ILight; import com.demo.bridge.lights.impl.CrystalLight; import com.demo.bridge.lights.impl.IncandescentLight; import com.demo.bridge.switchs.BaseSwitch; import com.demo.bridge.switchs.RemoteControlSwitch; /** * Created by Daniel on 2018/3/18. * 客户端应用程序 */ public class ClientForBridge { public static void main(String[] args) { //白炽灯实例 ILight incandescentLight = new IncandescentLight(); //水晶灯实例 ILight crystalLight = new CrystalLight(); //一般开关 System.out.println("---一般开关---"); BaseSwitch switch1 = new BaseSwitch(incandescentLight); switch1.makeLight(); System.out.println("\n---遥控开关---"); RemoteControlSwitch remoteControlSwitch = new RemoteControlSwitch(crystalLight); remoteControlSwitch.makeRemoteLight(1); } }
运行结果:
2. 让遥控开关遥控白炽灯
如果我们想要用遥控开关控制白炽灯,我们只需要将白炽灯传入遥控开关的构造方法中即可实现。
将遥控开关部分:
RemoteControlSwitch remoteControlSwitch = new RemoteControlSwitch(crystalLight);
修改为:
RemoteControlSwitch remoteControlSwitch = new RemoteControlSwitch(incandescentLight);
运行结果:
正如运行结果,遥控开关可以控制白炽灯了!
8.7 使用场合
1)不希望在抽象类和它的实现部分之间有一个固定的绑定关系;
2)类的抽象及实现都应该可以通过生成子类的方法加以扩充;
3)对一个抽象的实现部分的修改对客户不产生影响,即客户的代码不必重新编码。