Design Pattern学习笔记之适配和外观(the Adapter and Facade Pattern)

Design Pattern学习笔记之适配和外观(the Adapter and Facade Pattern)

1.    引子--Whois?

a.      应用适配器模式可实现你想不到的伟大工作:将方的木柄塞到圆的孔洞中。

b.      适配器模式跟装饰模式类似,都用于封装对象,但两者目的不同。

c.      适配器模式有对象装饰模式和类装饰模式两种实现方式。

d.      外观模式用于简化多个子系统(或者多个类)的使用。

e.      外观模式并不封装子系统(或多个类),因此客户端依然可以直接使用子系统的方法。

2.    类比—现实生活中的适配器

生活中很多适配器的例子,都知道联想的笔记本在国外的价格几乎是国内价格的一半,买个水货的本挺划算,但是一般要带一个电源的转接头,转接头一头插入我们国家标准的三孔电源插座;另一面提供国外使用的特殊插孔,供笔记本的电源插头使用。你看上面的转接头就是一个适配器:提供电源的插座是“中国接口”,笔记本的电源插头需要的是“美国接口”,怎么样让两者都协同工作呢?使用转接头,将电源插座的“中国接口”转换成适合笔记本使用的“美国接口”。

简单的说,适配器实现接口的转换工作,假如我们之前的系统需要用到第三方提供的“三角形”接口,我们的调用都是基于“三角形”接口的,现在第三方的接口发生变化了,变成了“半圆形”的接口,我们可以用适配器进行接口的转换从而还让原系统在旧接口上工作。图示如下:

3.    示例—火鸡变鸭子?

大家还记得我们在第一节介绍设计模式时说到的模拟鸭塘游戏,有个超级鸭子类,那我们现在把它做成接口,有两个方法:quack和fly;另外现在有个火鸡接口,也有两个方法:gobble和fly;现在我们使用适配器,让火鸡变鸭子。来看看代码:

鸭子接口

public interface Duck {

    public void quack();

    public void fly();

}

野鸭子

public class MallardDuck implements Duck {

    public void quack() {

       System.out.println("Quack");

    }

 

    public void fly() {

       System.out.println("I'mflying");

    }

}

火鸡接口

public interface Turkey {

    public void gobble();

    public void fly();

}

野生火鸡

public class WildTurkey implements Turkey {

    public void gobble() {

       System.out.println("Gobblegobble");

    }

 

    public void fly() {

       System.out.println("I'm flyinga short distance");

    }

}

火鸡适配器(火鸡变鸭子)

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();

       }

    }

}

测试一下

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 Turkeysays...");

       turkey.gobble();

       turkey.fly();

 

       System.out.println("\nThe Ducksays...");

       testDuck(duck);

 

       System.out.println("\nThe TurkeyAdaptersays...");

       testDuck(turkeyAdapter);

    }

 

    static void testDuck(Duck duck) {

       duck.quack();

       duck.fly();

    }

}

总结下适配器模式:

1. 客户端使用目标接口调用适配器类

2. 适配器类通过调用“被适配”类的接口(跟目标接口不同),实现接口转换

3. 适配器返回客户端结果,客户端不知道适配器进行过接口的转换

4.    不辨不明—没有傻问题

Q:有多少“adapting”的工作需要做?看起来如果接口比较庞大的话,需要做的工作会比较多。

A:对于很多方法的接口而言,应用适配器模式确实需要做很多工作,不过,设想下,如果不用该模式,就要修改系统中所有调用系统接口的地方,这样有很大的风险;应用适配器模式后,我们把变化都封装到一个类里面,设计更清爽。

Q:适配器模式是否仅封装一个类?

A:适配器模式的目的是将一个接口转换为另外一个接口,当然很多教课书中的例子都是对一个类做适配,在实际应用中某个适配器有可能需要多个“被适配”的类才能完成工作,在这种应用场景下适配器模式跟后面要讲的外观模式比较像,以后我们会做详细的比较。

Q:如果一个系统中既有使用老接口的部分又有使用新接口的部分,那应该怎么应用适配器模式?

A:使用双向适配器(既实现新接口也实现老接口)

5.    理论篇—适配器模式的定义

The AdapterPattern converts the interface of a class into another interface the clientsexpect. Adapter lets classes work together that couldn’t otherwise because ofincompatible interfaces.

适配器模式把类原有的接口转换成客户端期望的另外一种接口,从而使得由于接口不兼容而不能一起工作的类能一起工作。

适配器模式的类图:

上述图示实现的适配器模式对于OO设计准则的应用:

1.      组合

2.      面向接口编程

3.      封装变化

4.      松散耦合

6.    实践篇—适配器模式的另一种实现

一般来说,适配器模式有两种实现,在官方定义中的实现是应用组合技术的对象方式实现;另外一种是应用多重继承技术的类实现,以下是类结构图:

适配器CAdapter通过继承既实现了CTarget的接口,又实现了Adaptee的接口,从而既可以作为CTarget使用,也可作为Adaptee使用。

         对象适配和类适配的优缺点:

1.      对象适配使用组合技术,可运行期改变;可应用于“被适配”类的所有子类(以超级鸭子为例,只要组合的类是一个超级鸭子即可);需要实现Target接口。

2.      类适配使用继承技术,运行期不可改变;只能适配特定类(抽象类或者接口的某一实现,以鸭子超级鸭子为例);所有接口都已实现,并可进行重载。

7.    实践篇—现实世界中的适配器

早期JDK版本中在所有集合类型(Vector、Stack、Hashtable等)实现elements方法,用于返回Enumeration类型的变量,Enumeration提供了对集合中元素的遍历方法,使得用户无需了解具体的集合是怎么实现的。Enumeration提供的接口主要有:hasMoreElement();nextElement()。

在以后的JDK版本中,集合类开始使用Iterator接口来替代Enumeration接口,Iterator提供的接口为:hasNext();next();remove()。

如果你的系统原来是针对Enumeration书写的,那现在换成Iterator接口,应该怎么做?对,应用适配器模式,让原有系统能在Iterator上依然能用。来看看类图:

8.    不辨不明—装饰和适配器

1.      装饰模式和适配器模式都实现了对类的封装。

2.      装饰模式的核心是职责,当我们讨论装饰模式时,目的是要为原有的设计增加新的行为和新的职责。

3.      适配器模式的核心是接口转换,把类的接口转换成客户端希望的样子(可以是一个类或者是多个类),从而使得由于接口不同而不能一起工作的类能在一起共工作。

4.      适配器模式实现了客户端和被调用者的松散耦合(中间加入了适配器类)。

5.      装饰模式可能被应用多次,装饰模式自身可能再被装饰。

6.      在为系统增加新的职责和行为时,应用装饰模式可以不用修改已有代码;在系统更换成新的第三方接口类,接口发生变化时,应用适配器模式可以不用修改已有代码。

9.    外观模式—问题引入

外观模式和适配器模式类似,也进行接口转换,但是它的目的是为了提供更简单易用的接口。假设我们设计了一套家庭影院,里面包含多个设备,以下是多个设备的控制类:

假定我们要看电影时,需要进行以下工作:

1.      将客厅灯设置成影院模式

2.      放下投影屏幕

3.      打开投影机

4.      将投影机的输入设置为DVD

5.      将投影机的屏幕模式设置为宽屏

6.      打开音响

7.      将音响的输入设置为DVD

8.      设置音响为环绕立体声模式

9.      设置音响的音量

10.  打开DVD

11.  控制DVD放电影

如果我们的控制系统按照以上方式使用家庭影院系统,就要再放电影时执行不同电器控制类的11个方法,同样,在放映结束的时候,要倒叙执行11个不同电器控制类的结束方法(假设电器关闭需要有先后顺序)。

这些还没有完,假如组成家庭影院的某个设备发生了更换,控制类发生变化时,控制系统就得更改;如果我们要用家庭影院系统听CD、听广播,要做的事情一样复杂;再者,如果我们想设计一个家庭影院的遥控器,这么复杂的操作,几乎是不可能完成的任务。

应用外观模式,可以让控制系统从这些复杂的电器控制中逃离出来,由外观实现类HomeTheaterFacade对涉及的各个电器进行“封装”,对外提供简单的操作接口。

来看看类图:

HomeTheaterFacade类对外提供简单的方法watchMovie和endMovie,封装了实现watchMovie时对各种电器的复杂调用,家庭影院的控制系统现在实现个遥控器就简单多了,调用一次看电影方法就能实现看电影。

另外HomeTheaterFacade类和其他电器的控制类处于同等位置,外观类并没有真正封装各个电器的控制类,只是提供了一种更简单的使用方式,客户端依然可以直接使用各种电器的控制类。

10.             没有傻问题—外观模式

Q:外观模式封装了复杂子系统的类,那如果其他客户端有直接调用子系统中类的需求,应该怎么处理?

A:外观模式并没有封装子系统中的类,只是通过组合子系统的多个类(或者多个子系统的多个类)提供有业务含义的更方便的使用方式,原有子系统的类依然能使用。

Q:外观模式增加了新的功能还是直接将需求简单转交给“封装”的子系统?

A:外观模式有能力增加新的功能,因为它可以足够聪明(比如说在家庭影院的例子中,外观类知道电器的开关顺序)。

Q:是否每个子系统只能有一个外观类?

A:每个子系统可以有多个外观类,具体数量要看实际需求和不同设计。

Q:在子系统的接口已经足够简单的场景下,应用外观模式有什么价值?

A:外观模式除了提供子系统的简单使用外,还实现了客户端和子系统之间的松散耦合,当子系统发生变化时,客户端代码可保持不变。

Q:是否可以说适配器模式和外观模式的主要区别是适配器模式封装了一个类,而外观模式表示要涉及多个类?

A:不是这样的,适配器模式主要目的实现接口的转换,在实现该功能的过程中,也可能会通过调用多个类来实现;外观模式主要目的是简化客户端的使用,在实现该功能的过程中,也可能仅引用一个类来实现。两者的主要区别不在于封装的类的数量,而在于封装这些类的目的。

11.             外观模式的应用—家庭影院外观类

我们来看看应用外观模式的家庭影院控制系统的外观类。

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,

               Projectorprojector,

               Screen screen,

               TheaterLightslights,

               PopcornPopperpopper) {

 

       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 readyto 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("Shuttingmovie theater down...");

       popper.off();

       lights.on();

       screen.up();

       projector.off();

       amp.off();

       dvd.stop();

       dvd.eject();

       dvd.off();

    }

}

12.             理论篇—外观模式定义

The FaçadePattern provides a unified interface to a set of interfaces in a subsystem. Façadedefines a higher-level interface that makes the subsystem easier to use.

外观模式为子系统的一组接口提供一个统一的接口,从而定义比直接使用子系统接口更高层次的接口,简化子系统的使用。

关键点:外观模式通过提供简单接口来简化子系统的使用。

13.             理论篇—最少知道准则(Least Knowledge)

最少知道原则指引我们尽量减低对象间的交互(耦合),来看看定义:

Principle ofLeast Knowledge – talk only to your immediate friend. 只跟关系最密切的对象打交道。

该准则阻止我们设计出有大量对象耦合在一起的系统,这样的系统不好维护,难于理解。

看看以下的代码,有多少个类耦合在一起?

Station.getThermometer().getTemperature();

怎么才能达到该原则?在对象的方法中进行方法调用时,只能调用以下这些方法:对象自己的方法;作为参数传递到该方法的对象的方法;该方法创建(或者实例化)的对象的方法;该对象通过组合技术持有对象的方法。不能调用其他方法返回对象的方法。

为什么不能调用其他方法返回对象的方法?

因为如果这样做的话,就增加了对返回对象的耦合;一般这样的处理是在前一个类中实现该方法,对客户端隐藏前一个类通过组合另外一个类来实现该方法的事实。

没有应用该准则的例子:

Public floatgetTemp(){

                   Thermometer thermometer =station.getThermometer();

                   Returnthermometer.getTemperature();

}

应用该准则后:

Public floatgetTemp(){

                   Returnstation.getTemperature();

}

再看一个合法应用的例子:

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(){

    }

}

14.             没有傻问题—最少知道准则

Q:迪米特法则(the Law ofDemeter)和这个法则是什么关系?

A:两个说的是一回事,只是在本书中使用了“最少知道法则”这个名字,因为这个更直观。

Q:应用这个法则有什么坏处?

A:使用该法则后增加了程序复杂度,降低了程序的运行效率,因此建议在需要的时候应用该法则。

Q:jdk中有一个我们经常使用的语句违反了该法则,你知道是什么?

A:System.out.println();

15.              Review

新模式:

The AdapterPattern converts the interface of a class into another interface the clientsexpect. Adapter lets classes work together that couldn’t otherwise because ofincompatible interfaces.

适配器模式把类原有的接口转换成客户端期望的另外一种接口,从而使得由于接口不兼容而不能一起工作的类能一起工作。

The FaçadePattern provides a unified interface to a set of interfaces in a subsystem. Façadedefines a higher-level interface that makes the subsystem easier to use.

外观模式为子系统的一组接口提供一个统一的接口,从而定义比直接使用子系统接口更高层次的接口,简化子系统的使用。

新准则:

Principle ofLeast Knowledge – talk only to your immediate friend. 只跟关系最密切的对象打交道。

模式回顾:

1.      当你需要使用一个接口和你需要的接口不一致的类时,应用适配器模式。

2.      当你需要简化复杂子系统使用时,应用外观模式。

3.      适配器模式将接口转换成客户端需要的那种。

4.      外观模式实现客户端和复杂子系统的松散耦合。

5.      实现适配器模式的工作量依赖于要实现的目标接口的大小以及复杂度。

6.      实现外观模式需要使用组合技术,在外观类中持有子系统的对象。

7.      适配器模式有类和对象两种实现方式,类实现应用继承技术,对象实现应用组合技术。

8.      可以对一个子系统应用多次外观模式。

9.      适配器模式封装一个对象以改变它的接口;装饰者模式封装一个对象以增加新职责;外观模式“封装”一组对象以简化使用。

OO准则:

a. 封装变化,encapsulate what varies

b. 组合优于继承, favorcomposition over inheritance

c. 面向接口编程,program to interfaces, not implementation

d. 致力于实现交互对象之间的松散耦合, strive for loosely coupled designs between objects that interact

e. 类应该对于扩展开发,对于修改封闭, classes should be open for extension but closed for modification

f. 依赖于抽象类或者接口而不是具体类。Depend on abstraction. Do not depend on concrete classes.

g. 只跟关系最密切的对象打交道。Principle of Least Knowledge – talk only to your immediate friend.

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值