天天看點

【HeadFirst 設計模式學習筆記】7 擴充卡模式和外觀模式

1.擴充卡的作用:将一個接口轉換為另一個接口。我們可以比喻為一個插座的轉換頭。

2.構造擴充卡的關鍵:實作了目标接口,并持有被适配者的執行個體。 而擴充卡使用的方法是:客戶通過目标接口調用擴充卡的方法對擴充卡送出請求,擴充卡使用被适配者接口把請求轉換為被支配者的一個或多個調用接口。我們舉一個火雞冒充鴨子的擴充卡例子:

我們定義兩個東西:

一個是鴨子:

public interface Duck {

    public void quack();

    public void fly();

}

一個是火雞:

public interface Turkey {

    public void gobble();

我們現在做的就是要讓火雞有能力去冒充鴨子,那麼我們必須把火雞的一些能力映射到鴨子具備的一些能力中去,于是我們就采用了一個負責處理這樣映射的類——擴充卡類:

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

        amp.setCd(cd);

        amp.setStereoSound();

        cd.on();

        cd.play(cdTitle);

    public void endCd() {

        System.out.println("Shutting down CD...");

        cd.eject();

        cd.off();

    public void listenToRadio(double frequency) {

        System.out.println("Tuning in the airwaves...");

        tuner.on();

        tuner.setFrequency(frequency);

        amp.setTuner(tuner);

    public void endRadio() {

        System.out.println("Shutting down the tuner...");

        tuner.off();

我們使用這個外觀模式就可以簡單舒适的看一場電影了:

public class HomeTheaterTestDrive {

        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方法中涉及了一個調用傳回的對象。

而我們把這個方法拆開就可以得到一個符合這個原則的案例:

        Thermometer thermometer = station.getThermometer();

        return getTempHelper(thermometer);

    public float getTemperHelp(Thermometer thermometer){

        return thermometer.getTemperature();

但是這有意義嗎?在這個簡單的例子中恐怕是沒有的。其實我們經常用到違反該原則的例子,比如System.out.println……,這就告訴我們這并非是金科玉律。有好有壞吧, 凡事有得有失,在外觀模式中,我們看到HomeTheaterFacade 類是遵循這個原則的,這就主要帶來好處:一個元件的更換和更新不影響我們通過HomeTheaterTestDrive這個測試類中的主函數去輕松的看一場電影。

線上視訊:

<a href="http://v.youku.com/v_show/id_XMjU2Njk1Mzc2.html">http://v.youku.com/v_show/id_XMjU2Njk1Mzc2.html</a>

<a href="http://v.youku.com/v_show/id_XMjU2Njk0NzE2.html">http://v.youku.com/v_show/id_XMjU2Njk0NzE2.html</a>

繼續閱讀