DesignPattern之Adapter和Facade
迪米特原则(”Least Knowledge”原则)
Adapter 适配器模式(主要目的:转化接口!)
1.定义
将一个类的接口,转换成客户期望的另一个接口。适配器让原来接口不兼容的类可以合作无间。
个人理解:适配器模式,就好比转换插。我的电脑的充电器是英标三脚的插头,无法插进欧标的二脚插座,要解决这个问题,只需要一个欧标转换插,电脑充电器插入这个转换插,然后这个转换插再插进欧标的插座就可以解决。而且不仅是电脑充电器,凡是英标三脚的插头的电器都能使用这个转换插(只要实现了接口,被适配者的任何子类,都可以搭配)。
适配器模式可以通过创建适配器进行接口转换,让原来不兼容的接口变成兼容。这可以让客户从实现的接口解耦。如果在一段时间后想要改变接口,适配器可以将改变的部分封装起来,客户就不必为了应对不同的接口而每次跟着修改。
适配器模式充满着良好的OO设计原则:使用对象组合,以修改的接口包装被适配者(适配器这个类中持有被适配者的引用)–> 额外优点:被适配者的任何子类都可以搭配适配器使用。
- 因为客户是和接口绑定起来(面向接口编程),而不是和实现绑定起来,所以可以使用数个适配器,每一个负责转换不同的后台类;或者也可以加上新的实现,==只要实现了目标接口即可!==
2.例子:
使用火鸡搭配适配器来冒充鸭子
鸭子接口:
public interface Duck {
public void quack();
public void fly();
}
绿头鸭实现了鸭子这个接口:
public class MallarDuck implements Duck {
@Override
public void quack() {
System.out.println("Quack!");
}
@Override
public void fly() {
System.out.println("MallarDuck flying!");
}
}
火鸡接口:
public interface Turkey {
public void gobble();
public void fly();
}
火鸡的实现类:
public class WildTurkey implements Turkey {
@Override
public void gobble() {
System.out.println("Gobble gobble!");
}
@Override
public void fly() {
System.out.println("I am flying a short distance!");
}
}
现在缺少鸭子对象,想用一些火鸡对象来冒充鸭子
–> 使用火鸡-->鸭子
适配器!!!!
// 首先这个适配器类需要实现想转换成的类型的接口,也就是客户所期望看到的接口
// 就好像前面所说的转换插,如果这个转换插不实现期望的接口(不是二脚欧标插口),
// 那么这个转换插并没有什么卵用!
//
public class TurkeyAdapter implements Duck {
// 需要取得适配的对象(被适配者)的引用
private Turkey turkey;
public TurkeyAdapter(Turkey turkey) {
this.turkey = turkey;
}
//实现了期望的接口就必须实现接口的方法,这里相当于委托给被适配者来实现
@Override
public void quack() {
turkey.gobble();
}
@Override
public void fly() {
for (int i = 0; i < 5; i++) {
turkey.fly();
}
}
}
客户类:
public class AdapterTest {
public static void main(String[] args) {
MallarDuck mDuck = new MallarDuck();
WildTurkey turkey = new WildTurkey();
// 将火鸡包装进一个火鸡适配器中,使他看起来像是一只鸭子
// 注意这里的类型是鸭子接口,就是说在客户看来这是一只鸭子
Duck turkeyAdapter = new TurkeyAdapter(turkey);
System.out.println("\nThe Turkey says...");
turkey.gobble();
turkey.fly();
System.out.println("\nDuck says...");
testDuck(mDuck);
System.out.println("\nThe turkeyAdapter says...");
// 因为turkeyAdapter的类型是Duck,所以完全可以作为参数传进testDuck方法
// 但Duck的外表下其实是一种火鸡。。。坑爹啊!!!
testDuck(turkeyAdapter);
}
private static void testDuck(Duck duck) {
duck.quack();
duck.fly();
}
}
3.java中的适配器模式
java早期的集合类型(Vector,Stack, Hashtable等)都有一个名为elements()
的方法,该方法的返回值类型为Enumeration
, Enumeration
里有两个方法hasMoreElements()
, nextElement()
。
Sun推出更新后的集合类是,开始使用Iterator
接口,与Enumeration
类似,但还提供了remove()
方法。
面对遗留代码,这些代码暴露出枚举类接口,但是我们想在新的代码中使用迭代器?
–> 使用一个适配器即可。
按照前面鸭子的例子:
Enumeration接口:
public interface Enumeration {
public void hasMoreElements();
public void nextElement();
}
Iterator接口:
public interface Iterator {
public boolean hasNext();
public Object next();
public void remove();
}
重头戏:
适配器!
public class EnumerationAdapter implements Iterator {
private Enumeration enum;
public EumerationAdapter(Enumeration enum) {
this.enum = enum;
}
@Override
public boolean hasNext() {
return this.enum.hasMoreElements();
}
@Override
public void next() {
this.enum.nextElement();
}
// 我们知道枚举不支持删除,因为枚举是一个只读的接口。
// 适配器无法实现一个有实际功能的remove方法
// 幸运的时, 迭代器接口的设计者事先料到了会有这样的需要
// 所以将remove()方法定义成会抛出 UnsupportedOperationException
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
2. Facade 外观模式(主要目的:简化接口!)
定义
–
提供了一个统一的接口,用来访问子系统中的一群接口。外观模式定义了一个高层接口, 让子系统更容易使用。
例子
–
假设家里有一套家庭影院
观赏电影(复杂的方式)
- 打开爆米花机
- 开始爆米花
- 将灯光调暗
- 放下屏幕
- 打开投影机
- 将投影机的输入切换到DVD
- 投影机设置成宽屏模式
- 打开功放
- 将功放的输入设置为DVD
- 将功放设置为环绕立体声
- 打开DVD播放器
- 开始播放DVD
映射成代码:
popper.on();
popper.pop();
lights.dim(10);
screen.down();
projector.on();
projector.setInput(dvd);
projector.wideScreenMode();
amp.on();
amp.setDvd(dvd);
amp.setSurroundSound();
amp.setVolume(5);
dvd.on();
dvd.play();
妈蛋,如果软甲设计成这么麻烦,哪里会有客户会用这个软件!!!
所以要使用==外观模式==,通过实现一个体更合理的接口的外观类,将一个复杂的子系统变得易用。
在这里,可以把上面的代码封装进一个watchMovie()
方法中,客户只需要调用这个方法,就可以完成一系列的准备工作(好比按下遥控器上的一个按钮,然后系统就自动准备好。)
public class HomeTheaterFacade {
AMplifier amp;
Tuner tuner;
DvdPlayer dvd;
cdPlayer cd;
Projector projector;
TheaterLights lights;
Screen screen;
PopcornPopper popper;
public HomeTheaterFacad(AMplifier amp,
Tuner tuner,
DvdPlayer dvd,
cdPlayer cd,
Projector projector,
TheaterLights lights,
Screen screen,
PopcornPopper popper) {
this.amp = amp;
this.tuner = tuner;
this.dvd = dvd;
this.cd = cd;
this.projector = projector;
this.screen = screen;
this.lights = lights;
this.popper = popper;
}
//外观模式!!!
public void watchMovie(String movie) {
popper.on();
popper.pop();
lights.dim(10);
screen.down();
projector.on();
projector.setInput(dvd);
projector.wideScreenMode();
amp.on();
amp.setDvd(dvd);
amp.setSurroundSound();
amp.setVolume(5);
dvd.on();
dvd.play();
}
}
客户类:
public class Client {
public static void main(String[] args) {
HomeTheaterFacade homeTheater = new HomeTheaterFacade(amp,
tuner, dvd, cd, projector, screen, lights, popper);
// 客户只需要调用watchMovie方法便可以看电影了,
// 比没有使用外观模式之前方便了无数倍!!!!
homeTheater.watchMovie("Matrix");
}
}
注意:外观模式并没有“封装“子系统的类,只是提供了简化的接口。所以如果客户觉得有必要,仍然可以直接使用子系统的类。这也是外观模式的一个优点:==提供简化的接口的同时,依然将系统外争的功能暴露出来,以供需要的人使用。==
3.迪米特原则(”Least Knowledge”原则)
–
这个原则告诉我们要减少对象之间的交互,只留下几个”密友“,让朋友圈维持在最小状态!!!!
这是说:当你在设计一个系统的时候,不管是任何对象,你都要注意它所交互的类有哪些,并注意这些类是如何交互的。
==–>不要让太多的类耦合在一起(低耦合!!!), 免得修改系统中一部分,会影响到其他部分==。
如果许多类之间相互依赖,那么这个系统就会变成一个易碎的系统,而且极其复杂(想象一下一堆毛线缠绕在一起。。。)
如何做到这个原则?
–
就任何对象而言,在该对象的方法内,只应该调用属于以下范围的方法:
该对象本身的
被当做方法的参数而传递进来的对象
此方法所创建或实例化的对象
前面三条告诉我们:如果某对象调用其他方法的方法,不要调用该对象的方法!!!!
对象的任何组件 (该类的属性的对象(该类持有对方引用))
调用从另一个调用中返回的对象的方法,会有什么坏处?
–
如果这样做,相当于想另一个对象的子部分发送请求,从而增加了直接认识的对象数目 –> 该类依赖的类的数目增加,系统的稳定性下降!
例子1
–
不采用这个原则:
public float getTemp() {
Thermometer thermometer = station.getThermometer();
return thermometer.getTemperature();
}
这里的termometer对象时由station的方法返回而来,这样做的结果是:当前的类与Thermometer这个类产生了关联,相当于当前类要认识Thermometer和Station两个类。。。
==采用这个原则==
在Station中添加一个方法,用来向Thermometer取得温度,这样就可以从Station这个类返回温度, 当前类无需与Thermometer产生关联(降低耦合!)
public float getTemp() {
return station.getTemperature();
}
外观模式和最少知识原则
–
回想一下前面的家庭影院的设计,如果不采用外观模式,那么客户这个类必须要与Tuner,Screen,CdPlayer等各种类打交道,类与类之间的联系十分复杂。但是采用外观模式之后,客户只需要与HomeTheaterFacade这个类打交道,调用watchMovie方法便可。类与类之间的关联、复杂度大大降低了!
试着让子系统也能遵守这个原则:如果子系统太复杂,有太多的朋友牵涉其中,那么我们可以增加更多的外观,将此子系统分成好几个层次。
装饰者、适配器、外观三种模式对比
模式 | 目的 |
---|---|
装饰者 | 不改变借口,但加入新功能、新责任 |
适配器 | 将一个接口转换成用户需要的接口 |
外观 | 使接口更简单易用 |