【HeadFirst 设计模式学习笔记】7 适配器模式和外观模式
作者:gnuhpc
出处:http://www.cnblogs.com/gnuhpc/
1.适配器的作用:将一个接口转换为另一个接口。我们可以比喻为一个插座的转换头。
2.构造适配器的关键:实现了目标接口,并持有被适配者的实例。 而适配器使用的方法是:客户通过目标接口调用适配器的方法对适配器发出请求,适配器使用被适配者接口把请求转换为被支配者的一个或多个调用接口。我们举一个火鸡冒充鸭子的适配器例子:
我们定义两个东西:
一个是鸭子:
public interface Duck {
public void quack();
public void fly();
}
一个是火鸡:
public interface Turkey {
public void gobble();
public void fly();
}
我们现在做的就是要让火鸡有能力去冒充鸭子,那么我们必须把火鸡的一些能力映射到鸭子具备的一些能力中去,于是我们就采用了一个负责处理这样映射的类——适配器类:
public class TurkeyAdapter implements Duck {
Turkey turkey;
public TurkeyAdapter(Turkey turkey ) {
this.turkey = turkey;
}
public void quack() {
turkey.gobble();
}
public void fly() {
for(int i=0; i < 5; i++) {
turkey.fly();
}
}
}
在这个类似于一种能力转换器的类中,我们首先指定了我们向转换为Duck的能力(implements Duck),而这个能力的实际提供者——Turkey则被我们以成员变量的形式置于这个类内部(Turkey turkey;)并且在这个类生成对象时传入(public TurkeyAdapter(Turkey turkey ) )。下一步就是具体定义这些能力的时候了——也就是把接口定义的方法都对应实现。
在使用的时候,我们的意图是让火鸡冒充鸭子,那么我们要先建立一个火鸡,然后建立一个能力转换器(new TurkeyAdapter(turkey)) ,把我们建立好的火鸡传进去处理,这样得到的一个对象(turkeyAdapter )就可以完全当做一个鸭子使用了。
public class DuckTestDrive {
public static void main(String[] args) {
WildTurkey turkey = new WildTurkey();
Duck turkeyAdapter = new TurkeyAdapter(turkey);
testDuck(turkeyAdapter);
}
static void testDuck(Duck duck) {
duck.quack();
duck.fly();
}
}
这样的设计体现了良好的OO设计原则:使用对象组合,包装被适配者。并且它是通过接口进行组合将二者绑定起来,而不是实现——这就是一个对象适配器的设计观念。
3.类适配器与对象适配器不同之处在于,类适配器使用继承的方式,多重继承了被适配者(此例中为火鸡)和目标适配者(此例中为鸭子)两者。而对象适配器则实现了鸭子的接口,在具体调用时则通过内部的火鸡成员变量提供具体真实的能力,通过这样的方式将两者组合起来。通过对比,我们能得到如下一些特点:
对象适配器不但能适配某一个类,而且还可以适配该类的任意子类,另外实现的方法可以由多个方法搭配完成,这样更具有弹性。
类适配器则只在需要的时候使用覆盖来实现一些方法,而不用像对象适配器一样实现整个被适配者的各种方法,因为它可以直接使用继承,更加有效率。
另外需要说明的是:由于Java中无法提供多重继承,所以无法轻易实现类适配器这个层面的东西。
4.在Sun Java中一个实际使用适配器的例子就是Iterator接口,它实现了对集合类型的遍历。
5.你可能发现一个事情:装饰者模式和适配器模式貌似比较相像,我们做一下比较:
装饰者需要有一些新的行为或者职责要加入到设计中,并且动态的进行添加处理,而适配器模式则是需要将一个能力转换为另一个能力,静态敲定了一些能力。装饰者也可以做到能力转换,而且还支持新行为的加入,适配器只是装饰者的一种变体,都是用来包装对象的。而从另外的角度,适配器一定会对接口进行转换,而装饰者一定不会。从意图上说,装饰者是被改变接口而扩展包装对象的行为或者职责,而适配器则是为了转换包装对象的行为来改变接口。
6.书中接着引入另一个改变接口的新模式——外观模式(facade),它改变接口的目的是简化接口。外观类没有对子系统进行封装,只是提供了集合式简化的接口。这其实是一个极朴素的概念,不只是简化了接口,也将客户从组件的子系统中解耦出来。外观和适配器都可以包装多个类,但是外观的意图在于简化接口,而适配器的意图在于将接口转换成不同的接口。
书中举了家庭影院的外观模式例子,将许许多多的组件动作结合在一起完成了一系列对用户来讲方便的方法:
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,
Screen screen,
TheaterLights lights,
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) {
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(dvd);
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 void listenToCd(String cdTitle) {
System.out.println("Get ready for an audiopile experence...");
lights.on();
amp.on();
amp.setVolume(5);
amp.setCd(cd);
amp.setStereoSound();
cd.on();
cd.play(cdTitle);
}
public void endCd() {
System.out.println("Shutting down CD...");
amp.off();
amp.setCd(cd);
cd.eject();
cd.off();
}
public void listenToRadio(double frequency) {
System.out.println("Tuning in the airwaves...");
tuner.on();
tuner.setFrequency(frequency);
amp.on();
amp.setVolume(5);
amp.setTuner(tuner);
}
public void endRadio() {
System.out.println("Shutting down the tuner...");
tuner.off();
amp.off();
}
}
我们使用这个外观模式就可以简单舒适的看一场电影了:
public class HomeTheaterTestDrive {
public static void main(String[] args) {
Amplifier amp = new Amplifier("Top-O-Line Amplifier");
Tuner tuner = new Tuner("Top-O-Line AM/FM Tuner", amp);
DvdPlayer dvd = new DvdPlayer("Top-O-Line DVD Player", amp);
CdPlayer cd = new CdPlayer("Top-O-Line CD Player", amp);
Projector projector = new Projector("Top-O-Line Projector", dvd);
TheaterLights lights = new TheaterLights("Theater Ceiling Lights");
Screen screen = new Screen("Theater Screen");
PopcornPopper popper = new PopcornPopper("Popcorn Popper");
HomeTheaterFacade homeTheater =
new HomeTheaterFacade(amp, tuner, dvd, cd,
projector, screen, lights, popper);
homeTheater.watchMovie("Raiders of the Lost Ark");
homeTheater.endMovie();
}
我们最后给外观模式一个定义:提供了一个统一的接口,用来访问子系统中的一群接口,外观定义了一个高层接口,让子系统更容易使用。
7.我们由此引入一个新的OO原则——最少知识(Least Knowledge)原则(也就是传说中的Law of Demeter):只和你的密友谈话。也就是说,当你正在设计一个系统时不管是任何对象,你都要注意它所交互的类有哪些,并注意它和这些类是如何交互的。尽量减少类之间的依赖。针对这个原则,我们有一系列的准则可以遵循:对于任何对象,在其内部的方法中,我们只应该调用属于以下范围的方法:
- 该对象本身。
- 被当做方法的参数而传递进来的对象。
- 此方法所创建或实例化的任何对象。
- 对象中的其他任何的对象成员和方法。
举一个汽车的例子:
public class car{
Engine engine;//类中的组件,我们可以调用它的方法
public Car(){
}
public void start(Key key){
Doors doors = new Doors();//方法创建的对象,我们可以调用它的方法
boolean authorized = key .turns();//传入的参数,我们可以调用它的方法
if(authorized){
engine.start();
updateDashboardDisplay();//对象中的方法,我们可以调用
doors.lock();
}
public void updateDashboardDisplay(){
}
}
不要对某个调用其他方法返回的对象进行方法的调用,我们若是这么做就相当于向另一个对象的子部分发请求,耦合性就会加大。但是这个原则也会带来一些弊端,导致更多的“包装”类被制造出来,以处理和其他组件的沟通,增加程序复杂度和运行时的性能。
我们再举书中一个练习题的例子仔细看看这个相当基本且重要的原则:
一个不符合这个原则的案例:
public class House{
WeatherStation station;
public float getTemp(){
return station.getThermometer().getTemperature();
}
}
这个getTemp方法中涉及了一个调用返回的对象。
而我们把这个方法拆开就可以得到一个符合这个原则的案例:
public class House{
WeatherStation station;
public float getTemp(){
Thermometer thermometer = station.getThermometer();
return getTempHelper(thermometer);
}
public float getTemperHelp(Thermometer thermometer){
return thermometer.getTemperature();
}
}
但是这有意义吗?在这个简单的例子中恐怕是没有的。其实我们经常用到违反该原则的例子,比如System.out.println……,这就告诉我们这并非是金科玉律。有好有坏吧, 凡事有得有失,在外观模式中,我们看到HomeTheaterFacade 类是遵循这个原则的,这就主要带来好处:一个组件的更换和升级不影响我们通过HomeTheaterTestDrive这个测试类中的主函数去轻松的看一场电影。
在线视频:
http://v.youku.com/v_show/id_XMjU2Njk1Mzc2.html
http://v.youku.com/v_show/id_XMjU2Njk0NzE2.html