适配器模式
场景描述:想象一下我们维护了一套老系统,并于一个厂商通过接口进行交互。但是最近厂商更新它们的代码,并变更了接口格式,我们如何在不修改老系统代码的基础上,优雅的过渡呢?
提出方案:我们可以想象一下我们平时使用的转换头,这就是一种适配器。我们可以通过这种方式来优雅的“将方形放入圆形中”。
我们尝试举一个例子,假设现在有一个鸭子接口
public interface Duck {
public void quack();
public void fly();
}
并作出一个实现
public class MallardDuck implements Duck {
@Override
public void quack() {
System.out.println("Quack");
}
@Override
public void fly() {
System.out.println("I'm 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'm flying a short distance");
}
}
我们如何能够将火鸡作为鸭子呢?可以提供一个适配器来做接口的转换
public class TurkeyAdapter implements Duck {
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 DuckTestDrive {
public static void main(String[] args) {
MallardDuck duck = new MallardDuck();
WildTurkey turkey = new WildTurkey();
Duck turkeyAdapter = new TurkeyAdapter(turkey);
System.out.println("The Turkey says...");
turkey.gobble();
turkey.fly();
System.out.println("\nThe Duck says...");
testDuck(duck);
System.out.println("\nThe TurkeyAdapter says...");
testDuck(turkeyAdapter);
}
static void testDuck(Duck duck){
duck.quack();
duck.fly();
}
}
可以通过结果看到我们现在将这个火鸡作为一只鸭子的实现了。
适配器模式将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。
我们看一下它的类图
实现一个适配器所需要进行的工作,和目标接口的大小成正比。
由于Java不支持多继承,所以适配器又分为“对象”适配器(Java)和“类”适配器(支持多继承的其他语言)。
再看一下“类”适配器的类图
外观模式
场景描述:我们现在有一个家庭影院,其中包含了DVD,投影机,自动屏幕,立体环绕声等等,每当我们想要看电影的时候,都需要进行一系列操作;而看完电影时,又需要反向再操作一遍。
改进方案:我们尝试进行一种近似流水线的改造,通过将一系列操作变成一个更加高层的操作。
如下是我们的改进方案的代码
public class HomeTheaterFacade {
Amplifier amp;
Tuner tuner;
DvdPlayer dvd;
CdPlayer cd;
Projector projector;
TheaterLights lights;
Screen screen;
PopcornPopper popper;
public HomeTheaterFacade(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.lights = lights;
this.screen = screen;
this.popper = popper;
}
public void watchMovie(String movie){
System.out.println("Get ready to watch a movie...");
popper.on();
popper.pop();
lights.dim(10);
screen.down();
projector.on();
projector.wideScreenMode();
amp.on();
amp.setDvd();
amp.setSurroundSound();
amp.setVolume(5);
dvd.on();
dvd.play(movie);
}
public void endMovie(){
System.out.println("Shutting movie theater down...");
popper.off();
lights.on();
screen.up();
projector.off();
amp.off();
dvd.stop();
dvd.eject();
dvd.off();
}
这样我们测试一下,只可以通过一步操作就完成之前的一系列操作
public class HomeTheaterTestDrive {
public static void main(String[] args) {
Amplifier amp = new Amplifier();
Tuner tuner = new Tuner();
DvdPlayer dvd = new DvdPlayer();
CdPlayer cd = new CdPlayer();
Projector projector = new Projector();
Screen screen = new Screen();
TheaterLights lights = new TheaterLights();
PopcornPopper popper = new PopcornPopper();
HomeTheaterFacade homeTheater = new HomeTheaterFacade(amp, tuner, dvd, cd, projector, lights, screen, popper);
homeTheater.watchMovie("Raiders of the Lost Ark");
System.out.println("\n-----------Watching Movies------------\n");
homeTheater.endMovie();
}
}
结果如下
外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。
外观模式有一个很好的特征:提供简化的接口的同时,依然将系统完整的功能暴露出来,以供需要的人使用。
以下是它的类图
适配器模式的意图是,“改变”接口符合客户的期望;而外观模式的意图是,提供子系统的一个简化接口。
我们将装饰者模式、适配器模式、外观模式一同比较,可以看出它们的意图分别为:
- 装饰者模式,不改变接口,但加入职责
- 适配器模式,将一个接口转成另一个接口
- 外观模式,让接口更简单
设计原则:最少知识原则:只和你的密友谈话。
当你正在设计一个系统,不管是任何对象,你都要注意它所交互的类有哪些,并注意它和这些类是如何交互的。
这个原则又名墨忒耳法则,它希望我们在设计中,不要让太多的类耦合在一起,免得修改系统中一部分,会影响到其他部分。
这个原则提供了一些方针:就任何对象而言,在该对象的方法内,我们只应该调用属于以下范围的方法:
- 该对象本身
- 被当作方法的参数而传递进来的对象
- 此方法所创建或实例化的任何对象
- 对象的任何组件
如果调用从另一个调用中返回的对象的方法,会有什么害处呢?如果我们这样做,相当于向另一个对象的子部分发请求(而增加我们直接认识的对象数目)。
这个原则的缺点是,采用这个原则会导致更多的“包装”类被制造出来,以处理和其他组件的沟通,这可能会导致复杂度和开发时间的增加,并降低运行时的性能。
总结如下:当我们需要从一个接口“转变”为另一个接口时,可以尝试使用适配器;而当我们想要将子系统的一系列操作简化时,可以使用外观模式。