設計模式,結構模式之適配器模式

1 概述

適配器模式(adapter pattern),從名字就可以看出,工作模式類似於適配器:將原本不兼容的兩樣事物連接,以協同工作。

2 適配器模式

充電器(電源適配器)是日常生活中常見的例子。大多手機要求輸入電壓是5V,而家用交流電的電壓都是220V,充電器作爲適配器,將220V的電壓轉爲目標電器需要的電壓。適配器模式也類似,通過適配器,將類的接口轉換爲目標所期望的另一個接口。
適配器模式開閉原則的體現,通過增加一個適配類,避免了對原有邏輯的修改。

3 案例

適配器模式主要有三種類型:類適配器,對象適配器,默認適配器。下面舉例說明。

3.1 類適配器

類適配器使用的是繼承模式,適配器類即是A接口,又是B接口。下面的例子展現了,如何通過適配器,使得“中國人”說“英語”:

public class Test {
    public static void main(String[] args) {
        // 普通ChineseSpeaker對象,只能輸出中文
        ChineseSpeaker xiaoming = new ChinesePeople();
        xiaoming.speak();
        // 被適配過的ChineseSpeaker對象,可以輸出英文
        ChineseSpeaker englishTeacher = new ChinesePeopleEnglishAdapter();
        englishTeacher.speak();
    }
}

public interface EnglishSpeaker {
    void talk();
}
public class EnglishPeople implements EnglishSpeaker {
    @Override
    public void talk() {
        System.out.println("Hello!");
    }
}

public interface ChineseSpeaker {
    void speak();
}
public class ChinesePeople implements ChineseSpeaker {
    @Override
    public void speak() {
        System.out.println("大家好");
    }
}

// 適配器類繼承了EnglishPeople,將speakChinese方法適配爲speakEnglish方法
public class ChinesePeopleEnglishAdapter extends EnglishPeople implements ChineseSpeaker {
    @Override
    public void speak() {
        this.talk();
    }
}

輸出:

大家好
Hello!

uml

ChineseSpeakerEnglishSpeaker是兩個不同的接口,而通過適配器類ChinesePeopleEnglishAdapter,使得ChineseSpeaker對象可以調用EnglishSpeaker的方法。

3.2 對象適配器

對象適配器使用的是組合模式,適配器實現A接口,並持有B接口的實例。下面的例子展現了,如何通過適配器,使得“普通人“可以“飛行”:

public class Test {
    public static void main(String[] args) {
        // 普通的Person對象,只能“跑”
        Person blackWidow = new Mortal("Natasha");
        blackWidow.move();
        IronSuit suit = new IronSuit();
        // 被適配了的對象,可以實現“飛”
        Person ironMan = new FlyablePersonAdapter("Tony Stark", suit);
        ironMan.move();
    }
}

public interface Person {
    void move();
}
public class Mortal implements Person {
    private String name;
    Mortal(String name) {
        this.name = name;
    }
    @Override
    public void move() {
        System.out.println(name + " is running!");
    }
}

public interface Flyable {
    void fly();
}
public class IronSuit implements Flyable {
    @Override
    public void fly() {
        System.out.println("I'm flying!");
    }
}

// 適配器類持有IronSuit實例,將move方法適配爲fly方法
public class FlyablePersonAdapter implements Person {
    private String name;
    IronSuit suit;
    FlyablePersonAdapter(String name, IronSuit suit) {
        this.name = name;
        this.suit = suit;
    }
    @Override
    public void move() {
        System.out.print(name + " is wearing Iron Suit: ");
        suit.fly();
    }
}

輸出:

Natasha is running!
Tony Stark is wearing Iron Suit: I'm flying!

uml

通過適配,可以讓Personmove()方法變爲Flyablefly()方法。

3.3 默認適配器

默認適配器適配器模式的變種,主要解決的問題是,當一個接口有多個方法時,有時候實現類只關心其中的部分方法。通過添加一個適配器類來給方法提供默認實現,可以實現這一需求:

public class Test {
    public static void main(String[] args) {
        People jay = new Singer();
        jay.speak();
        People yao = new Athlete();
        yao.move();
    }
}

public interface People {
    void eat();
    void sleep();
    void move();
    void speak();
}
public class PeopleAdapter implements People {
    @Override
    public void eat() {}
    @Override
    public void sleep() {}
    @Override
    public void move() {}
    @Override
    public void speak() {}
}

// 通過適配器,Athlete只需要實現move方法
public class Athlete extends PeopleAdapter {
    @Override
    public void move() {
        System.out.println("Athlete is running.");
    }
}
// 通過適配器,Singer只需要實現speak方法
public class Singer extends PeopleAdapter {
    @Override
    public void speak() {
        System.out.println("Singer is singing.");
    }
}

輸出:

Singer is singing.
Athlete is running.

uml

適配器類PeopleAdapter給接口中的方法提供了默認的實現(或是空實現),使得其子類可以只關心自己需要的方法。

4 使用

前兩種適配器模式,在給系統新增功能的時候非常有用,可以避免對原有邏輯的修改,降低系統的複雜度。比如JDK中我們熟悉的Callable接口,跟Runnable一樣,也可以新起一個線程。但這是JDK1.5中新增的接口,而新起線程是由Runnable的實現類Thread中的native方法實現的,那如何在原有基礎上,增加對Callable支持呢?答案就是適配器模式

public class FutureTask<V> implements RunnableFuture<V> {
    private Callable<V> callable;
    public FutureTask(Callable<V> callable) {
        this.callable = callable;
    }
    public void run() {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
                set(result);
        }
    }
}

Callable都會被包裝成一個FutureTask的實例,FutureTask實現了Runnable接口,可以作爲RunnableCallable兩個接口的適配器,這樣,我們不需要對原先Runnable的框架做任何修改。

而第三種適配器模式則主要運用在開發過程中,可以爲我們減少很多工作,易於開發。比較廣爲人知的便是NettyChannelHandlerAdapter,它爲開發者提供了接口中方法的空實現,降低了接口使用的難度。

4 總結

適配器模式符合開閉原則。當需要使兩個不兼容的接口一起工作時,適配器模式將是很好的選擇。

文中例子的github地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章