设计模式(二):适配器模式和外观模式
适配器模式
一、出国必备适配器
随着中国经济的快速发展,我们也越来越有钱了。出国旅游也不是什么新鲜事了。泰国是中国人最爱去的旅游地之一。当我们程序员去泰国旅游时,可能会带着笔记本电脑去,以便随时可以工作(不要问我为什么)。如果你带着你的惠普电脑去泰国游玩,你会发现泰国酒店的插座一般是这样的:。而你的电脑插头却是这样的:
。如果你要充电是不是尴尬了?所以在你去之前你要带上一个插座的适配器(转换插头):
。有了这个适配器你的电脑就可以充电了。现在我们将这个例子用代码展现出来。
1.首先我们有一个惠普电脑HpComputer,我们电脑充电需要的一个中国的插座接口ChinaOutlet
/** * 惠普电脑 * @author wuqi * @Date 2019/2/11 11:22 */ public class HpComputer { public void charge(ChinaOutlet chinaOutlet){ chinaOutlet.work(); } }
/** * 中国的插座 * @author wuqi * @Date 2019/2/11 11:33 */ public interface ChinaOutlet { void work(); }
2.但是当我们来到泰国时,却发现泰国的插座接口TailandOutlet并不适用于我们的电脑
/** * 泰国的插座 * @author wuqi * @Date 2019/2/11 11:45 */ public interface TailandOutlet { void work(); }
/** * 泰国酒店的插座 * @author wuqi * @Date 2019/2/11 11:42 */ public class HotelTailandOutlet implements TailandOutlet { @Override public void work() { System.out.println("HotelTailandOutlet work,charging..."); } }
3.所以我们需要一个适配器来适配泰国的插座接口,以便我们的电脑可以使用。让我们的适配器实现中国插座接口,并利用组合的方式将泰国的插座接口封装进来给我们的电脑提供充电的功能
/** * 泰国插座适配器 * @author wuqi * @Date 2019/2/11 13:47 */ public class TailandOutletAdapter implements ChinaOutlet { TailandOutlet tailandOutlet; public TailandOutletAdapter(TailandOutlet tailandOutlet){ this.tailandOutlet = tailandOutlet; } @Override public void work() { tailandOutlet.work(); } }
4.最后我们测试以下,可以看到,我们的电脑已经可以正常充电了
/** * 适配器模式测试 * @author wuqi * @Date 2019/2/11 13:49 */ public class AdapterPatternTest { public static void main(String[] args) { //泰国的插座 HotelTailandOutlet hotelTailandOutlet = new HotelTailandOutlet(); //泰国插座的适配器 ChinaOutlet chinaOutlet = new TailandOutletAdapter(hotelTailandOutlet); HpComputer hpComputer = new HpComputer(); hpComputer.charge(chinaOutlet); } }
二、定义适配器模式
上面的例子即是适配器模式,下面我们给适配器模式下一个定义
1.概念
将一个类的接口,转换成客户期望的另一个接口。适配器让原本不兼容的类可以合作无间。
2.概念解析
结合上面插座的例子,我们很好理解适配器模式的概念,如果有一个接口提供了我们需要的功能(泰国插座),但是我们现有的接口和该接口又相冲突(中国插座),我们就可以定义一个适配器(TailandOutletAdapter)来适配该接口,以此来兼容我们现有的接口。
3.UML类图
三、适配器模式的应用场景
适配器模式的定义很直观的反映出了适配器模式的应用场景,它适用于将一个接口转换成你所需要的另一个接口的场景。
四、适配器模式的优缺点
优点:适配器将原本不可使用的接口转换成了另一个接口,这可以让客户从实现的接口解耦。如果在一段时间之后,我们想要改变接口,适配器可以将改变的部门包装起来,客户不必为了应对不同的接口而每次跟着修改。
缺点:1.如果适配接口方法很多,那么适配器则需要一个一个去适配,这样做就会比较麻烦。上面插座的例子中,我们需要适配的方法只有一个work(),我们适配的工作量就很小,但是如果还有额外的几十甚至几百个方法时,那么我们还要去把这些额外的方法一个个给适配掉,毫无疑问,这是非常费时费力的。
2.如果在适配接口中有一个方法,但是被适配的接口却没有,那么目标方法将无法适配,这时则需要我们额外的进行处理。比如,如果我们要将Enumeration适配成Iterator,那么我们就需要适配Iterator的remove方法,但是Enumeration中并没有该功能,那么该功能就会适配失败,此时就需要我们对该方法进行额外处理。
外观模式
一、用外观模式改善生活
我们程序员的生活都很累,难得到周末,我们一般很少外出,基本都是窝在家里看电视(其实是看手机,假设我们窝在家里看电视)。假设现在是周日的中午,你吃过午饭,感觉很疲惫,这时你就想躺在家里舒舒服服的看个电视剧。而我们想舒舒服服的看个电视剧,我们不仅仅要打开电视,我们可能还要打开屋里的电灯,温度过高或过低我们还要打开空调,为了保证私密性,还要关上我们的电窗,最后,我们就可以打开电视做下舒舒服服的看电视剧了。下面我们用代码来展现出这个过程。
1.首先是我们需要操作的电器
/** * 电灯 * * @author wuqi * @Date 2019/2/11 14:48 */ public class Light { public void open(){ System.out.println("Light open..,"); } public void close(){ System.out.println("Light close..."); } }
/** * 空调 * @author wuqi * @Date 2019/2/11 14:45 */ public class AirCondition { public void open(){ System.out.println("AirCondition open"); } public void close(){ System.out.println("AirCondition close..."); } }
/** * 电窗 * @author wuqi * @Date 2019/2/11 14:47 */ public class PowerWindow { public void open(){ System.out.println("PowerWinodw open..,"); } public void close(){ System.out.println("PowerWindow close..."); } }
/** * 电视机 * @author wuqi * @Date 2019/2/11 14:44 */ public class TV { public void open(){ System.out.println("TV open..."); } public void close(){ System.out.println("TV closed..."); } }
2.最后我们进行观看电视剧
/** * 不使用外观模式观看电视剧 * @author wuqi * @Date 2019/2/12 10:04 */ public class SimpleTest { public static void main(String[] args) { Light light = new Light(); AirCondition airCondition = new AirCondition(); PowerWindow powerWindow = new PowerWindow(); TV tv = new TV(); light.open(); airCondition.open(); powerWindow.close(); tv.open(); } }
运行可以看到我们可以看电视剧了
但是,你会发现我们就是看个电视剧,却要一个个的去打开或关闭各个电器,麻烦的是如果我们看完电视剧后,我们还要对这些电器又重复操作一遍,我们现在只有四个电器还好点,如果我们有十几个电器需要操作,那真的就很麻烦了。
这时候外观模式就派上用场了,想一下,我们可以定义一个观看电视剧的外观,这个外观假设就是一个遥控器,遥控器上有两个按钮,一个观看电视的按钮,一个关闭电视的按钮。当我们按下观看电视的按钮时,不仅会打开电视还会将电灯打开,空调打开,关上电窗。关闭按钮按下时,就会起到反效果,这样我们看电视剧就会变得非常的简单美好。下面我们就来用外观模式来改善我们观看电视的过程。
1.定义我们的外观——遥控器
/** * 遥控器。将各个电器的操作整合在一起,充当了外观的角色。 * @author wuqi * @Date 2019/2/11 14:50 */ public class RemoteControlFacade { private Light light; private AirCondition airCondition; private PowerWindow powerWindow; private TV tv; public RemoteControlFacade(Light light, AirCondition airCondition, PowerWindow powerWindow, TV tv){ this.light = light; this.airCondition = airCondition; this.powerWindow = powerWindow; this.tv = tv; } /** * 看电视 */ public void pushOpenTvButton(){ light.open(); airCondition.open(); powerWindow.close(); tv.open(); } /** *关闭电视 */ public void pushCloseTvButton(){ light.close(); airCondition.close(); powerWindow.open(); tv.close(); } }
2.使用外观观看电视剧
/** * 外观模式测试 * @author wuqi * @Date 2019/2/11 15:01 */ public class FacadePatternTest { public static void main(String[] args) { Light light = new Light(); AirCondition airCondition = new AirCondition(); PowerWindow powerWindow = new PowerWindow(); TV tv = new TV(); //创建观看电视的外观类——遥控器 RemoteControlFacade remoteControlFacade = new RemoteControlFacade(light,airCondition,powerWindow,tv); //观看电视 remoteControlFacade.pushOpenTvButton(); //结束观看 remoteControlFacade.pushCloseTvButton(); } }
运行结果:
可以看到使用外观模式将我们观看电视的的操作变得十分的简单,十分的美好。
二、定义外观模式
1.概念
外观模式(Facade Pattern)它将一个或数个的类的复杂的一切都隐藏在背后,只显露出一个干净美好的外观。也就是简化了接口。
2.概念解析
对应于我们观看电视的例子我们来解析下这个概念。我们一开始观看电视时,是一个个的将电器打开,看完电视后,又将这些电器一个个关闭。后来将这些电器都封装在一个外观(遥控器)中提供出一个观看电视方法,一个关闭电视方法。而观看电视和关闭电视的方法其实就是调的各个电器的方法打开或关闭方法。遥控器就是将这些电器的操作隐藏在背后,只露出观看电视和关闭电视这个美好的外观。
3.UML类图
3.外观模式中的OO设计原则
1.最少知识原则
外观模式中遵守了一个OO的设计原则:最少知识原则(又叫做墨忒耳法则,有的网上叫做迪米特法则),只和你的密友交谈。
最少知识原则要求我们在设计一个系统时,不管是任何对象,你都要注意它所交互的类有哪些,并注意它和这些类是如何交互的。这个原则希望我们在设计中,不要让太多的类耦合在一起,免得修改其中的一部分,会影响到其他的部分。如果许多类之间相互依赖,那么这个系统就会变成一个易碎的系统,它需要花费许多维护成本,也会因为太复杂而不容易被他人了解。
很显然,我们的外观模式将复杂的子系统隐藏起来,只提供一个接口,减少了多个类之间的耦合,符合这个原则。
2.如何减少类之间的依赖
怎么样才能减少类之间的依赖,最少知识原则提供了一些方针,就任何对象而言,在该对象的方法内,我们只应该调用属于以下范围的方法:
- 该对象本身
- 被当作方法的参数而传进来的对象
- 此方法所创建或实例化的对象
- 对象的任何组件
下面通过一个代码的例子来说明:
三、外观模式的应用场景
外观模式适用于将复杂的子系统隐藏一起来,并对外提供一个简单外观,简化接口的场景
四、外观模式的优缺点
1.优点:外观模式不仅将复杂的子系统隐藏起来,并提供简单的外观,简化了接口。它也将客户端和复杂的子系统解耦
2.缺点:因为外观模式需要将复杂的子系统封装隐藏起来,所以会产生较多的小类,会增加系统的体积。
适配器模式和外观模式注意点
虽然大多数教科书所采用的例子中适配器只适配一个类,但是你可以选择适配许多类来提供一个接口让客户编码。类似的,一个外观也可以只针对一个拥有复杂接口的类来提供简化接口。两种模式的差异,不在于它们“包装了”几个类,而在于它们的意图。适配器的意图是,“改变”接口符合客户的期望;而外观模式的意图是,提供子系统的简化接口。