Default Adapter

December 27, 2021

在 Java 8 之前,介面只用來定義行為外觀,實作介面時要將全部行為實現,例如,Web 容器環境要實作生命週期傾聽器時要實作的 ServletContextListener,在 Java EE 8(基於 Java SE 7)之前是這麼定義:

package javax.servlet;

import java.util.EventListener;

public interface ServletContextListener extends EventListener {
    public void contextInitialized(ServletContextEvent sce);
    public void contextDestroyed(ServletContextEvent sce);
}

或許你只想處理 Web 應用程式初始化,然而兩個方法都得實作,contextDestroyed 沒什麼事,方法本體只好空著:

public class GossipListener implements ServletContextListener {
    public void contextInitialized(ServletContextEvent sce) {
        ...實作應用程式初始化
    }

    public void contextDestroyed(ServletContextEvent sce) {}
}

ServletContextListener 只定義了兩個方法,空著一個方法本體也許還好,不過有的介面定義了一大堆方法,若你只對其中一個方法感興趣,其他方法都得空著,程式寫起來麻煩,閱讀時也不方便吧!

Java 8 之前的配接器

在 Java SE 8 之前,有個方式是先定義一個抽象類別來實作該介面,例如:

public abstract class ServletContextAdapter implements ServletContextListener {
    public void contextInitialized(ServletContextEvent sce) {}
    public void contextDestroyed(ServletContextEvent sce) {}
}

如果你只想處理 Web 應用程式初始化,繼承 ServletContextAdapter 後只要重新定義 contextInitialized 就可以了:

public class GossipListener extends ServletContextAdapter {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ...實作應用程式初始化
    }
}

ServletContextAdapter 就像個配接器,讓你只處理感興趣的,這算是實現了關注分離吧!

Java 8 以後的配接器

Java SE 8 以後,介面可以定義預設方法,基於 Java 8 的 Java EE 8,ServletContextListener 的定義變成:

package javax.servlet;
import java.util.EventListener;
public interface ServletContextListener extends EventListener {
    public default void contextInitialized(ServletContextEvent sce) {}
    public default void contextDestroyed(ServletContextEvent sce) {}
}

因為介面允許有預設的方法實作,若只想處理 Web 應用程式初始化,實作 ServletContextListener 時只要重新定義 contextInitialized 就可以了:

public class GossipListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ...實作應用程式初始化
    }
}

在過去以抽象類別實作配接器的一些程式庫或框架,若已經採用了 Java SE 8 作為基礎,多半也改用介面預設方法來實現,例如 Spring 的 Web 組態檔必須實作 WebMvcConfigurer 的行為,這個介面要實作的方法蠻多的,在 Spring 5 之前,會繼承 WebMvcConfigurerAdapter,針對感興趣的行為重新定義。

Spring 5(基於 Java SE 8)以後,WebMvcConfigurer 有定義預設方法,WebMvcConfigurerAdapter 也就被廢棄了。

只有 Java 才需要?

這個模式好像是 Java 才會用?不!就算是動態定型語言也可有這類需求,動態定型語言採用鴨子定型(Duck typing),雖然不用像 Java 一定要用 interface 明確地寫出行為規範,不過還是得遵守行為協定,不然執行時期會引發 AttributeErrorTypeError 之類的問題。

具有 adapter 字眼的模式,代表著會有一方制定了規範,而另一方必須滿足規範,要滿足規範的那方就是 adapter。

Default Adapter 就是提供預設實作的 adapter,主要概念是,如果你的函式需要一組行為,而客戶端可能只對其中幾個行為感興趣,可以這組行為提供預設動作(不一定是空動作),不強迫客戶端處理他們不感興趣的東西。

因此廣義來說,其實很多地方都看得到 Default Adapter 的概念,例如 JavaScript 的 JSON.stringify,如果你指定的物件有 toJSON,就會用 toJSON 的定義轉換為 JSON,不然就有預設動作,不是嗎?

別因為模式有個名稱,就把它當成是個什麼了不起的東西,如果你撰寫程式時,隨時檢視程式碼、視需求變化、維護觀點適當重構與調整…在這個過程中,你可能有些類似的思考方式,那就是模式了。

哪天你跟一群人溝通你的想法並建立了共識,日後為了便於溝通,再想想看有什麼大家都能接受的適當名稱,就只是這樣罷了,思考才是最重要的!