适配器模式将一个类的接口,转换为客户期望的另一个接口。适配器让原本不兼容的类可以合作无间
定义一般都是让人看不懂的,我们结合实例来理解。
先用现实生活中的例子:
如图,美国人到欧洲旅游时都会遇到一个问题,美国的插头和欧洲的插座并不兼容。怎么办呢?通过一个交流电适配器就可以了。
再看软件开发中的例子:
假设现在有一个软件系统,想要使用新的厂商提供的类库。
再具体点,假设现有系统是一个交易所系统,它从前都是调用的Trade接口的inMoney()方法来跟银行通信进账。而现在新厂商提供了一个新接口NewTrade的transferMoneyIn()方法来进账。怎么办?
交易所系统里面的所有进账功能都是调用的Trade接口的方法,而新厂商的新接口NewTrade并没有继承Trade,所以不能直接使用。在不修改双方代码的前提下,我们就可以使用适配器模式来完成这个需求:
下面,我们用代码来详细说明上面的场景:
系统旧的交易类支持广发银行、招商银行等:
/** * 旧的交易类,支持广发银行、招商银行等等 */ public interface Trade { /** * 入账方法 */ public void inMoney(double money); } /** * 与广发银行进行出入金的交易类 */ public class TradeFromCGB implements Trade{ @Override public void inMoney(double money) { System.out.println("从广发银行入账"+money+"元"); } }现在我们和新的厂商以及银行合作,新厂商扩展提供了支持浦发银行、工商银行等的新的交易类:
/** * 新厂商提供的交易类 */ public interface NewTrade{ /** * 入账方法 */ public void transferMoneyIn(double money); } /** * 与工商银行进行出入金的交易类 */ public class NewTradeFromICBC implements NewTrade{ @Override public void transferMoneyIn(double money) { System.out.println("从工商银行入账"+money+"元"); } }现在出现问题了,在原来的系统里面,所有跟交易相关的方法都是调用的旧交易类接口进行的操作。
在不修改系统的前提下,我们无法调用新接口与工商银行、浦发银行进行交易。
/** * 交易系统 */ public class TradeSystem { /** * 往交易系统的银行账户上入账 * 形参都是Trade接口,新厂商提供的NewTrade无法适配 */ public void addMoneyToSystemAccount(Trade trade,double money) { trade.inMoney(money); } }怎么办,修改系统的代价太大,所有交易相关的方法都要改,而联系厂商改动接口也不现实,资金困难的我们要多付钱。现在只有自力更生了,我们想到了设计一个适配器类来伪装新接口:
/** * 新交易适配器类 */ public class NewTradeAdapter implements Trade{ NewTrade newTrade; public NewTradeAdapter(NewTrade newTrade) { this.newTrade = newTrade; } @Override public void inMoney(double money) { newTrade.transferMoneyIn(money); } }通过实现旧接口,以及组合新接口,在旧接口的方法中调用新接口的对应方法,我们伪装成功!
测试:
public class Main { public static void main(String[] args) { TradeSystem tradeSystem = new TradeSystem(); Trade trade = new TradeFromCGB(); tradeSystem.addMoneyToSystemAccount(trade, 1800000.5); NewTrade newTrade = new NewTradeFromICBC(); NewTradeAdapter newTradeAdapter = new NewTradeAdapter(newTrade); tradeSystem.addMoneyToSystemAccount(newTradeAdapter, 1800000.5); } }/**output: 从广发银行入账1800000.5元 从工商银行入账1800000.5元 */现在,我们可以顺利的调用“新接口”从工商银行里面入账了。
外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观模式定义了一个高层接口,让子系统更容易使用。
外观模式其实就是对复杂接口的一种简化,当你觉得使用的相关子接口又多又复杂时,可以在它们上面设计出一个外观类,进行统一。
现在我们有一个复杂的家庭影院系统,它设计到了许多组件:爆米花机、投影仪、屏幕、灯光……
每次放映一次电影,需要许多组件的合作:
//打开爆米花机 popper.on(); //开始准备爆米花 popper.pop(); //调暗光线 lights.dim(); //屏幕放下 screen.down(); //打开投影仪 projector.on(); //放入DVD,开始看电影 dvd.on(movie);除了放映电影,当我们看完要关闭仪器时,又得调用所有的组件反操作一次,实在是太麻烦啦!
我们思考,难道不可以把所有的相关类组合进一个类中,设计出一个更加合理更简洁的外观类吗?
组合了所有相关类后的家庭影院“外观类”:
/** * 家庭电影院 */ public class HomeTheaterFacade { Popper popper; Lights lights; Screen screen; Projector projector; Dvd dvd; public HomeTheaterFacade(Popper popper,Lights lights,Screen screen,Projector projector,Dvd dvd) { this.popper = popper; this.lights = lights; this.screen = screen; this.projector = projector; this.dvd = dvd; } /** * 观看电影 */ public void watchMovie(){ //打开爆米花机 popper.on(); //开始准备爆米花 popper.pop(); //调暗光线 lights.dim(); //屏幕放下 screen.down(); //打开投影仪 projector.on(); //放入DVD,开始看电影 dvd.on(movie); } /** * 停止看电影 */ public void endMovie() { popper.off(); lights.off(); screen.up(); projector.off(); dvd.off(); } }通过外观模式,我们简化了复杂的子系统接口,也将客户从组件的子系统中解耦。
另外,虽然外观模式简化了子系统,但依然可以直接使用子系统的类,系统的完整功能依旧暴露在外,供需要的人使用。
外观模式还涉及到了一个新的设计原则:
最小知识原则:减少对象之间的交互,只留下几个“密友”。
这个原则具体点说就是,我们在设计系统时,需要注意不让太多的类耦合在一起。 如果许多类之间相互依赖,那么这个系统就会变成一个易碎的系统,它将需要花许多成本维护,也会因为太复杂而不容易被其他人了解。
这个原则提供了一些方针
就任何对象而言,在该对象的方法内,我们只应该调用属于以下范围的方法:
该对象本身被当做方法的参数而传递进来的对象此方法所创建或实例化的任何对象对象的任何组件举个常见的反例,这句代码耦合了两个类:
System.out.println();这三个模式看起来的比较像,都是通过组合包装实现。
区别在于:
适配器模式是将一个对象包装起来以改变其接口。装饰者模式是将一个对象包装起来以增加新的行为和责任。外观模式是将一群对象包装起来以简化其接口。本文总结自: 《Head First 设计模式》第七章:适配器模式与外观模式