Scala 與設計模式(五):Adapter 適配器模式

本文由 Prefert 發表在 ScalaCool 團隊博客。html

無論你是否是果粉,確定對 iphone X 都有所耳聞。最近的「掉漆門」和「人臉識別被破解」更是將其推到了風口浪尖上。git

可是對於我而言,最難以忍受的仍是耳機接口被取消這一改變(自 Iphone 7 開始),你能夠想象這樣一幅畫面:當你開開心心地和小夥伴開着語音吃(song)着(kuai)雞(di)或者是多人一塊兒上(song)分時——你的電量見底,爲了避免影響隊友(shou)的遊戲體驗,確定得充電玩下去。github

這時你得面對一個難題:只有一個孔,插耳機仍是插電源!?(在沒有藍牙耳機的前提下)數據庫

![](https://user-gold-cdn.xitu.io/2017/11/20/15fd89a0ab870701?w=960&h=540&f=jpeg&s=41550)

(侵刪)編程

因爲生活常常會欺騙咱們,以及各類環境因素,因此不是每一個人都選擇藍牙耳機(貧窮使我理智)。設計模式

是否存在別的解決方法呢?還好有轉接線這樣的好東西app

![](https://user-gold-cdn.xitu.io/2017/11/20/15fd89a0ac243914?w=430&h=430&f=jpeg&s=10931)

(侵刪)框架

在編程中,咱們也會趕上相似的問題:iphone

  1. 當你想使用一個已經存在的類,而它的接口不符合你的需求;
  2. 你想建立一個能夠複用的類,該類能夠與其餘不相關的類或不可預見的類協同工做;
  3. ...

本文會經過 Adapter Pattern 來探究如何解決這類問題。ide

本篇文章結構以下:

  • adapter pattern 的概念
  • 實際問題分解
  • Java 實例
  • Scala 實例
  • 總結

概念

適配器模式(Adapter Pattern)有時候也稱包裝樣式或者包裝(Wrapper)。定義以下:

將一個類的接口轉接成用戶所期待的。一個適配使得因接口不兼容而不能在一塊兒工做的類能在一塊兒工做,作法是將類本身的接口包裹在一個已存在的類中。

它解決了什麼問題

適配器模式將現有接口轉化爲客戶類所指望的接口,實現了對現有類的複用,它是一種使用頻率很是高的設計模式,在軟件開發中得以普遍應用,在 Spring 等框架、驅動程序設計(如 JDBC 中的數據庫驅動程序)中也使用了適配器模式。

Java 版本

小 A 是個蘋果控 + 耳機控,以前買了一款很貴的耳機,對其愛不釋手。咱們都知道通常耳機接口都是 3.5mm 的。

public interface PhoneJackInterface {
    // 傳統的播放音頻
    void audioTraditionally();
}

public class PhoneJackConnector implements PhoneJackInterface {
    @Override
    public void audioTraditionally() {
        System.out.println("經過 PhoneJack 播放聲音");
    }
}
複製代碼

iphone 7 以前的 iphone 支持 3.5mm 接口:

public class Iphone {
   private PhoneJackInterface phoneJack;

    public Iphone(PhoneJackInterface phoneJack) {
        this.phoneJack = phoneJack;
    }

    // Iphone 具有播放聲音的功能
    public void play() {
        // 經過 3.5mm 接口耳機播放
        phoneJack.audioTraditionally();
    }
}
複製代碼

這樣的狀況下,小 A 還能夠愉快地聽歌:

// test
PhoneJackInterface phoneJack = new PhoneJackConnector();
Iphone iphone6 = new Iphone(phoneJack);
iphone6.play();
// 控制檯輸出 「經過 PhoneJack 播放聲音」
複製代碼

在 iphone 7 問世後,問題出現了:小 A 發現其不支持 3.5mm 接口 —— 將有線耳機的插口改成了 lightning

public interface LightningInterface {
    void audioWithLightning();
}

public class LightningConnector implements LightningInterface {
    @Override
    public void audioWithLightning() {
        System.out.println("經過 Lightning 播放聲音");
    }
}
複製代碼

一邊是耳機,一邊是手機,這太難以抉擇了。但蘋果怎麼可能沒考慮到這點了,能夠經過贈送的耳機轉接器 —— 將傳統的耳機頭轉爲 lightning

public class HeadsetAdapter implements PhoneJackInterface { // 基於傳統耳機接口

   LightningInterface lightning; // 兼容新接口

    /** * 傳入 lightning 接口 * @param lightning */
    public HeadsetAdapter(LightningInterface lightning) {
        this.lightning = lightning;
    }

    /** * 對傳統接口兼容 */
    @Override
    public void audioTraditionally() {
        lightning.audioWithLightning();
    }
}
複製代碼

類適配器

這樣不夠簡潔,咱們能夠改一改:

public class HeadsetAdapter extends LightningConnector implements PhoneJackInterface {
    @Override
    public void audioTraditionally() {
        // 傳統接口兼容 lightning
        super.audioWithLightning();
    }
}
複製代碼

測試:

// test
HeadsetAdapter headsetAdapter = new HeadsetAdapter();
Iphone iphone7 = new Iphone(headsetAdapter);
iphone7.play();
// 控制檯輸出 「經過 Lightning 播放聲音」
複製代碼

對象適配器

咱們通常將上面的適配器稱做「類適配器」,除此以外還有一種 「對象適配器」,咱們能夠對適配器類進行以下修改:

public class ObjectHeadsetAdapter implements PhoneJackInterface { // 基於傳統耳機接口

   LightningConnector lightning; // 兼容新接口

    /** * 傳入 lightning 接口 * @param lightning */
    public ObjectHeadsetAdapter(LightningConnector lightning) {
        this.lightning = lightning;
    }

    /** * 對傳統接口兼容 */
    @Override
    public void audioTraditionally() {
        // 使用委託實現兼容
        this.lightning.audioWithLightning();
    }
}
複製代碼

測試:

ObjectHeadsetAdapter objectHeadsetAdapter = new ObjectHeadsetAdapter(new LightningConnector());
Iphone iphoneX = new Iphone(objectHeadsetAdapter);
iphoneX.play();
複製代碼

對象適配器 vs 類適配器

經過以上簡單的例子,相信你對適配器模式有一個大體瞭解了。「類適配器」與「對象適配器」的區別歸納以下:

- 類適配器 對象適配器
建立方式 須要經過建立自身建立出一個新的 Adapter 能夠經過已有的 Adapter 對象來轉換接口
擴展性 經過 Override 來擴展新需求 由於包含關係因此不能擴展
其餘 繼承被適配類,因此相對靜態 包含被適配類,因此相對靈活

優勢

總的來講,適配器模式主要有如下幾個優勢:

  1. 將目標類和適配者類解耦,經過引入一個適配器類來重用現有的適配者類,無須修改原有結構。
  2. 增長了類的透明性和複用性,將具體的業務實現過程封裝在適配者類中,對於客戶端類而言是透明的,並且提升了適配者的複用性,同一個適配者類能夠在多個不一樣的系統中複用。
  3. 靈活性和擴展性都很是好,經過使用配置文件,能夠很方便地更換適配器,也能夠在不修改原有代碼的基礎上增長新的適配器類,徹底符合「開閉原則」。

看完 Java 的實現方式,咱們再來看看 Scala 是如何實現的。

Scala 版本

在 Scala 中,因爲方便的語法糖,咱們並不須要像 Java 那樣麻煩,已知傳統接口類(此處省略一些接口)

class PhoneJackConnector {
   def audioTraditionally = println("經過 PhoneJack 播放聲音")
}
複製代碼

若是咱們有須要適配的,爲其建立一個 trait 便可:

trait Lightning {
  def audioWithLightning()
}
複製代碼

其次再新建一個類,繼承傳統類:

class HeadsetAdapter extends PhoneJackConnector with Lightning {
  override def audioTraditionally: Unit = super.audioTraditionally

  override def audioWithLightning: Unit = println("經過 Lightning 播放聲音")
}
複製代碼

你會開心的發現:在這個新的類裏,咱們能夠對新老方法一塊兒擴展——在 Java 中,這是「對象適配器」和 「類適配器」比較大的一個劣勢。
測試:

val headsetAdapter = new HeadsetAdapter
headsetAdapter.audioTraditionally
複製代碼

固然,除了這種方式,Scala 裏還能夠經過隱式轉換來實現適配 final 類的適配器:

final class FinalPhoneJackConector {
    def audioTraditionally = println("經過 PhoneJack 播放聲音")
  }

object FinalPhoneJackConector {
    implicit class ImplictHeadsetAdapter(phoneJackConnector: FinalPhoneJackConector) extends Lightning {
      override def audioWithLightning: Unit =  println("經過 Lightning 播放聲音")
    }
}
複製代碼

測試:

val headsetAdapter = new HeadsetAdapter
headsetAdapter.audioTraditionally

// 隱式
val light: Lightning = new FinalPhoneJackConector
light.audioWithLightning()
複製代碼

Hint: 對於不熟悉 implicit 的朋友能夠 看一下這裏

總結

光從代碼量來講,Scala 簡潔比 Java 表現的好太多。

其次,Scala 結合了「類適配器」和「對象適配器」全部的優勢,並消除了自身問題。與 Java 相比,Scala 有以下特色:

  1. 與對象適配器同樣靈活
  2. 與「類適配器相比」,沒有對特定被適配類的依賴
  3. 只適用於不須要動態改變被適配類的狀況

源碼連接 若有錯誤和講述不恰當的地方還請指出,不勝感激!

相關文章
相關標籤/搜索