Adapter樣式取代部分實作的介面

Adapt Interface

Joshua Kerievsky               透明

 

你的類別實作一個介面,但是只為介面中的一部分方法提供程式碼。

將實作的方法移交給該介面的適配器(adapter),並讓一個工廠方法可以存取(accessible)該適配器。

 

關鍵字

      Adapter,設計樣式,重整,Java

 

動機

      具體類別中的空方法總是讓我很煩心。我經常看見一些空方法,因為具體類別經常需要通過實作某個介面來滿足某種要求,但又不是真的需要實作介面中所有的方法。不需要的方法得到了聲明,但是保留為空:添加它們僅僅是為了滿足編譯器的規則。這可能不會讓你煩心,但的確讓我很煩心。我發現,類別的介面(即 public 方法集合)中如果出現空方法,不但會使類別的介面變得臃腫,還會對類別的行為做出錯誤的宣傳(我是一個類別,我可以做 X() Y() Z()——但我其實只提供 X() 的程式碼),而且強迫我做一些不想做的工作(比如聲明一些空方法)。

 

      Adapter樣式提供了一種重整這種程式碼的好方法。Adapter類別將給介面中所有的方法一個空的實作,而你則可以從Adapter類別延生出需要的子類別,同時只需要提供需要的程式碼。在Java中,我甚至不需要正式聲明一個Adapter子類別:我只需要在Adapter類別中建構一個匿名子類別,然後直接將它的參照提供給Factory Method就行了。

 

溝通

重複

簡化

不管是有人忘了刪掉空方法還是因為介面強迫你保留空方法,類別中的空方法都不會有太多的溝通。最好是只在真正實作的時候才進行溝通,而 Adapter可以幫助你實作這一點。

如果你有不止一個類別部分實現了同一介面,那麼你可能擁有相當多的空方法了。只要讓這些類別都與一個處理空方法聲明的 Adapter 類別協同工作,你就可以去掉這些重複。

提供少量程式碼總比提供大量程式碼要容易。這個重整方法給你一種方法以減少類別中聲明的方法數量。另外,當你用這種方法來適配多個介面時,它可以提供一條非常好的途徑,讓你可以實作每介面中的一部分方法。

 

技巧

1.         如果還沒有要使用的介面(我把它叫做 A)的適配器,就用適配介面的方法來建構一個。然後建構一個 Factory Method 樣式,讓它傳回你的適配器的一個實例(我把它叫做 AdapterInstance)。

2.       把你的類別中那些完全因為實作 A 介面才存在的空方法掉。

3.       把你實作了的那些由 A 指定的方法轉移到 AdapterInstance 中。

4.       刪除你的類別中“implements A”的聲明。

5.       AdapterInstance 提供給需要的客戶。

 

範例

       我將用具體的程式碼來說明上面的步驟。在這裡我們有一個名叫 CardComponent 的類別,它衍生自 JDK中的 Component 類別,並實作 JDK 中的 MouseMotionListener 介面。但是,它只實作 MouseMotionListener 介面的兩個方法中的一個。讓我們看看 Adapter 樣式怎樣改善這段程式碼。

 

第一步,為我們的 AdapterInstance 建構一個 Factory Method。如果我們還沒有 AdapterInstance,我們就需要用適配介面的重構方法來建構一個。在這裡, JDK 已經為 MouseMotionListener 介面提供了一個適配器,名叫 MouseMotionAdapter。所以我們在 CardComponet 類別中建構下列新方法,其中使用了 Java 便利的內部類別能力:

 

private MouseMotionAdapter createMouseMotionAdapter() {

return new MouseMotionAdapter() {

};

}

 

       接著,我們 CardComponent 中宣告的空方法,因為它在 MouseMotionListener 已經被實作了。在這裡,MouseMotionListener 實作了 mouseDragged 方法,但沒有實作 mouseMoved 方法。

 

public void mouseMoved(MouseEvent e) {}

 

現在我們已經將mouseDragged方法從CardComponent方法中移到了MouseMotionAdapter的實例中。

 

private MouseMotionAdapter createMouseMotionAdapter() {

return new MouseMotionAdapter() {

public void mouseDragged(MouseEvent e) {

e.consume();

dragPos.x = e.getX();

dragPos.y = e.getY();

setLocation(getLocation().x+e.getX()-currPos.x,

getLocation().y+e.getY()-currPos.y);

repaint();

}

};

}

 

       現在我們可以從CardComponentMouseMotionListener的實作了。

 

    public class CardComponent extends Container implements MouseMotionListener {

 

       最後,我們必須將新的適配器實例提供給需要的客戶。在這裡,我們必須觀察建構函式,它的程式碼是這樣:

 

public CardComponent() {

//……

addMouseMotionListener(this);

}

 

需要將它改為呼叫新的私有工廠方法:

 

public CardComponent() {

//…

addMouseMotionListener(createMouseMotionAdapter());

}

 

       現在我們進行測試。很不幸,因為這段程式碼與滑鼠相關,我無法進行自動化單元測試。因此我進行了一些簡單的手工測試,並確認:我們的重整很成功。

 

附錄:原始程式碼

l       重整前:

public class CardComponent extends Container implements MouseMotionListener ...

public CardComponent(Card card,Explanations explanations) {

//...

addMouseMotionListener(this);

}

public void mouseDragged(MouseEvent e) {

e.consume();

dragPos.x = e.getX();

dragPos.y = e.getY();

setLocation(getLocation().x+e.getX()-currPos.x, getLocation().y+e.getY()-currPos.y);

repaint();

}

public void mouseMoved(MouseEvent e) {

}

 

 

l       重整後:

public class CardComponent extends Container ...

public CardComponent(Card card,Explanations explanations) {

//...

addMouseMotionListener(createMouseMotionAdapter());

}

private MouseMotionAdapter createMouseMotionAdapter() {

return new MouseMotionAdapter() {

public void mouseDragged(MouseEvent e) {

e.consume();

dragPos.x = e.getX();

dragPos.y = e.getY();

setLocation(getLocation().x+e.getX()-currPos.x, getLocation().y+e.getY()-currPos.y);

repaint();

}

};

}