适配器模式
定义
适配器模式将一个类的接口,转换为客户期望的另一个接口。适配器让原本不兼容的类可以合作无间
场景+代码
场景
定义一般都是让人看不懂的,我们结合实例来理解。
先用现实生活中的例子:
如图,美国人到欧洲旅游时都会遇到一个问题,美国的插头和欧洲的插座并不兼容。怎么办呢?通过一个交流电适配器就可以了。
再看软件开发中的例子:
假设现在有一个软件系统,想要使用新的厂商提供的类库。
再具体点,假设现有系统是一个交易所系统,它从前都是调用的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 设计模式》第七章:适配器模式与外观模式