一塊兒學設計模式 - 適配器模式

適配器模式(Adapter Pattern)屬於結構型模式的一種,把一個類的接口變成客戶端所期待的另外一種接口,從而使本來接口不匹配而沒法一塊兒工做的兩個類可以在一塊兒工做...html

<!-- more -->java

概述

當你想使用一個已經存在的類,而它的接口不符合你的需求,或者你想建立一個可重用的類(與不兼容接口無關的類),這時候能夠考慮使用適配器模式。同時它也是一種包裝模式,它與裝飾模式一樣具備包裝的功能。git

http://os71dlblu.bkt.clouddn.com/article/images/20171104/java/design-pattern/adapter-pattern/1.png

案例

筆者算是小米的忠實用戶了,從大學期間起至今都是購買的小米,期間發現小米5在推出的時候,它會送一個type-c的轉接口給咱們,那會type-c數據線應該還不算普及,這種作法仍是蠻好的,在使用轉接口後Micro USB得以重複利用,這樣一來即便原裝的米5數據線丟了也不要緊,只要有type-c轉接口,同樣能夠用Micro USB充電/鏈接電腦編程

類適配器

1.首先定義M4DataLine 表明是Micro USB,咱們目的就是經過適配器可以用米4數據線鏈接米5手機設計模式

class M4DataLine {
    public void connection() {
        System.out.println("使用小米4數據線鏈接...");
    }
}

2.定義客戶端使用的接口,與業務相關數組

interface Target {
    void connection();
}

class M5DataLine implements Target {
    @Override
    public void connection() {
        System.out.println("使用小米5數據線鏈接...");
    }
}

3.建立適配器類,繼承了被適配類,同時實現標準接口微信

class M5DataLineAdapter extends M4DataLine implements Target {

    @Override
    public void connection() {
        System.out.println("插入 type-c 轉接頭");
        super.connection();
    }
}

4.客戶端代碼,測試ide

public class AdapterMain {

    public static void main(String[] args) {
        Target target = new M5DataLine();
        target.connection();

        Target adapter = new M5DataLineAdapter();
        adapter.connection();
    }
}

5.結果測試

使用小米5數據線鏈接...
插入 type-c 轉接頭
使用小米4數據線鏈接...

對象適配器

建立適配器類,實現標準接口,將這個調用委託給實現新接口的對象來處理this

class M5DataLineAdapter implements Target {

    private Target target;

    public M5DataLineAdapter(Target target) {
        this.target = target;
    }

    @Override
    public void connection() {
        System.out.println("插入 type-c 轉接頭");
        target.connection();
    }
}

public class AdapterMain {

    public static void main(String[] args) {
        // 使用特殊功能類,即適配類
        Target adapter = new M5DataLineAdapter(new M5DataLine());
        adapter.connection();
    }
}

區別

類適配器:對象繼承的方式,靜態的定義。

對象適配器:依賴於對象的組合,都是採用對象組合的方式,也就是對象適配器實現的方式。

JDK 中的適配器使用

使用適配器模式的類

java.util.Arrays#asList()
java.io.InputStreamReader(InputStream)
java.io.OutputStreamWriter(OutputStream)

Java I/O 庫大量使用了適配器模式,如 ByteArrayInputStream 是一個適配器類,它繼承了 InputStream 的接口,而且封裝了一個 byte 數組。換言之,它將一個 byte 數組的接口適配成 InputStream 流處理器的接口。

OutputStream 類型中,全部的原始流處理器都是適配器類。ByteArrayOutputStream 繼承了 OutputStream 類型,同時持有一個對 byte 數組的引用。它一個 byte 數組的接口適配成 OutputString 類型的接口,所以也是一個對象形式的適配器模式的應用。

FileOutputStream 繼承了 OutputStream 類型,同時持有一個對 FileDiscriptor 對象的引用。這是一個將 FileDiscriptor 接口適配成 OutputStream 接口形式的對象型適配器模式。

Reader 類型的原始流處理器都是適配器模式的應用。StringReader 是一個適配器類,StringReader 類繼承了 Reader 類型,持有一個對 String 對象的引用。它將 String 的接口適配成 Reader 類型的接口。

Spring 中使用適配器模式的典型應用

在 Spring 的 AOP 裏經過使用的 Advice(通知)來加強被代理類的功能。Spring 實現這一 AOP 功能的原理就使用代理模式(一、JDK 動態代理。二、CGLib 字節碼生成技術代理。)對類進行方法級別的切面加強,即,生成被代理類的代理類,並在代理類的方法前,設置攔截器,經過執行攔截器中的內容加強了代理方法的功能,實現的面向切面編程。

Advice(通知)的類型有:BeforeAdvice、AfterReturningAdvice、ThrowSadvice 等。每一個類型 Advice(通知)都有對應的攔截器,MethodBeforeAdviceInterceptor、AfterReturningAdviceInterceptor、ThrowsAdviceInterceptor。Spring 須要將每一個 Advice(通知)都封裝成對應的攔截器類型,返回給容器,因此須要使用適配器模式對 Advice 進行轉換。

public interface MethodBeforeAdvice extends BeforeAdvice {
    void before(Method method, Object[] args, Object target) throws Throwable;
}

public interface AdvisorAdapter {

    boolean supportsAdvice(Advice advice);

    MethodInterceptor getInterceptor(Advisor advisor);
}

class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {

    @Override
    public boolean supportsAdvice(Advice advice) {
        return (advice instanceof MethodBeforeAdvice);
    }

    @Override
    public MethodInterceptor getInterceptor(Advisor advisor) {
        MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
        return new MethodBeforeAdviceInterceptor(advice);
    }

}

默認的適配器註冊表

public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Serializable {

    private final List<AdvisorAdapter> adapters = new ArrayList<AdvisorAdapter>(3);

    public DefaultAdvisorAdapterRegistry() {
        // 註冊適配器 
        registerAdvisorAdapter(new MethodBeforeAdviceAdapter());
        registerAdvisorAdapter(new AfterReturningAdviceAdapter());
        registerAdvisorAdapter(new ThrowsAdviceAdapter());
    }

    @Override
    public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException {
        if (adviceObject instanceof Advisor) {
            return (Advisor) adviceObject;
        }
        if (!(adviceObject instanceof Advice)) {
            throw new UnknownAdviceTypeException(adviceObject);
        }
        Advice advice = (Advice) adviceObject;
        if (advice instanceof MethodInterceptor) {
            // So well-known it doesn't even need an adapter.
            return new DefaultPointcutAdvisor(advice);
        }
        for (AdvisorAdapter adapter : this.adapters) {
            // 檢查是否支持,這裏調用了適配器的方法 
            if (adapter.supportsAdvice(advice)) {
                return new DefaultPointcutAdvisor(advice);
            }
        }
        throw new UnknownAdviceTypeException(advice);
    }

    @Override
    public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
        List<MethodInterceptor> interceptors = new ArrayList<MethodInterceptor>(3);
        Advice advice = advisor.getAdvice();
        if (advice instanceof MethodInterceptor) {
            interceptors.add((MethodInterceptor) advice);
        }
        for (AdvisorAdapter adapter : this.adapters) {
            // 檢查是否支持,這裏調用了適配器的方法 
            if (adapter.supportsAdvice(advice)) {
                interceptors.add(adapter.getInterceptor(advisor));
            }
        }
        if (interceptors.isEmpty()) {
            throw new UnknownAdviceTypeException(advisor.getAdvice());
        }
        return interceptors.toArray(new MethodInterceptor[interceptors.size()]);
    }

    @Override
    public void registerAdvisorAdapter(AdvisorAdapter adapter) {
        this.adapters.add(adapter);
    }

}

總結

優勢

  • 可讓任何兩個沒有關聯的類一塊兒運行
  • 提升了類的複用,想使用現有的類,而此類的接口標準又不符合現有系統的須要。經過適配器模式就可讓這些功能獲得更好的複用。
  • 增長了類的透明度,客戶端只關注結果
  • 使用適配器的時候,能夠調用本身開發的功能,從而天然地擴展系統的功能。

缺點

  • 過多使用會致使系統凌亂,追溯困難(內部轉發致使,調用A適配成B)

適用場景

  • 系統須要使用一些現有的類,而這些類的接口(如方法名)不符合系統的須要,甚至沒有這些類的源代碼。
  • 想建立一個能夠重複使用的類,用於與一些彼此之間沒有太大關聯的一些類,包括一些可能在未來引進的類一塊兒工做。

小故事

魏文王問名醫扁鵲說:「大家家兄弟三人,都精於醫術,到底哪一位最好呢?」
扁鵲答:「大哥最好,二哥次之,我最差。」
文王再問:「那麼爲何你最出名呢?」
扁鵲答說:「我大哥治病,是治病於病情發做以前。因爲通常人不知道他率先能剷除病因,因此他的名氣沒法傳出去,只有咱們家的人才知道。我二哥治病,是治病於病情初起之時。通常人覺得他只能治輕微的小病,因此他的名氣只及於本鄉里。而我扁鵲治病,是治病於病情嚴重之時。通常人都看到我在經脈上穿針管來放血、在皮膚上敷藥等大手術,因此覺得個人醫術高明,名氣所以響遍全國。」
比較起來,能防範於未然是最高明的,但每每因防範在前,不會出現惡果,使事物保持了原態,沒有「明顯」的功績而被忽略。正如不見防火英雄,只有救火英雄同樣。高明者不見得必定名聲顯赫。

建議儘可能使用對象的適配器模式,少用繼承。適配器模式也是一種包裝模式,它與裝飾模式一樣具備包裝的功能,此外,對象適配器模式還具備委託的意思。總的來講,適配器模式屬於補償模式,專門用來在系統後期擴展、修改時使用,但要注意不要過分使用適配器模式。

參考文獻:《大話設計模式》

IBM developerWorks:適配器模式原理及實例介紹

- 說點什麼

全文代碼:https://gitee.com/battcn/design-pattern/tree/master/Chapter5/battcn-adapter

  • 我的QQ:1837307557
  • battcn開源羣(適合新手):391619659

微信公衆號:battcn(歡迎調戲)

相關文章
相關標籤/搜索