JavaScript 設計模式 —— 介面卡模式

阿魚發表於2020-03-10

寫在前面:

該文章為個人學習記錄心得,如有謬誤,懇請大佬斧正指點。

簡介:

介面卡模式是將一個類(物件)的介面(方法或屬性)轉化為使用這個類的“客戶”所期望的另外一種介面(方法或屬性),介面卡模式使得提供介面的類和使用這個類的“客戶”從不相容而變得相容

個人理解:

服務使用方將為了適應服務提供方而產生的資料處理邏輯抽離,而封裝出一個新的類(介面卡),介面卡輸入原有服務提供方的介面,輸出服務使用方可以接受的介面。

好處:

  1. 使得資料處理的邏輯從服務接收方中抽離,簡化其邏輯。
  2. 使得服務接受方程式碼更加純粹,開發時,不再關心具體的資料處理細節。
  3. 使得服務提供類更加通用

一些 ?(Demo)

​ 我們都知道 IPhone(從4s以後)都是使用Lighting介面充電的,而近幾年的安卓手機大多是使用Type-C介面充電的。我們無法將Type-C充電器直接插到IPhone上,為Iphone充電。

​ 此時此刻我們可能需要一些程式碼來演示這個過程:

// 一個Iphone類
class IPhone {
  constructor(generation) {
    this.name = `IPhone${generation}`;
    this.usbMode = 'lighting';
    this.currentBattery = 0;
    this.tid = null;
  }
 // 充電方法
  getCharge(charger) {
   // 判斷輸入的是否是Lighting模式的充電器
    if(charger.type !== this.usbMode) {
      // 不是,丟擲型別錯誤, 拒絕充電。
      throw new TypeError(`charger Type error, expect ${this.usbMode}, but got a/an ${charger.type}`)
    }
    // 否則 執行charger提供的充電方法
    charger.startCharge(this)
  }
  removeCharger() {
    if (this.tid) clearInterval(this.tid);
    console.log(`電源已移除, 當前電量${this.currentBattery}`)
  }
}
// 一個充電器類
class Charger {
  constructor(type) {
    this.type = type
  }
  // 提供的充電方法
  startCharge(phone) {
    phone.tid = setInterval(() => {
      // 充滿後停止充電
      if(phone.currentBattery >= 100 ) {
        clearInterval(phone.tid);
        console.log('充電完畢,請移除電源')
      } else {
        // 如果沒有充滿,則每秒鐘給手機衝入1%的電(氪金快充)
        phone.currentBattery += 1;
        console.log(`${phone.name}正在充電, 當前電量${phone.currentBattery}%`)
      }
    }, 1000)
  }
}

// 我買一個IPhone 11 Pro Max
const iPhone11ProMax = new IPhone('IPhone 11 Pro Max')
// 手機被我用沒電了
iPhone11ProMax.currentBattery = 0;
// 再買一個Type-C充電器
const typec_charger = new Charger('type-c');
// 給手機充電吧
iPhone11ProMax.getCharge(typec_charger);

OK 充電失敗 (瀏覽器丟擲了一些錯誤)

VM3492:14 Uncaught TypeError: charger Type error, expect lighting, but got a/an type-c
    at IPhone.getCharge (<anonymous>:14:13)
    at <anonymous>:52:16

​ 這時候我們需要一個轉換器——type-c To Lighting 轉換器(CtoL):它提供Tyep-C的母口進行電能輸入,提供Lighting的公口進行輸出。

​ 機智的你,開啟了 京寶 / 拼東 / 淘多多......

​ 購買了一個Type-C 轉 Lighting的轉接頭(介面卡)

於是我們的介面卡就登場了!

// 一個 Type-C 到 Lighting 的介面卡類
class C2LAdapter {
  constructor({type, startCharge}) {
   this.type = type,
   this.startCharge = startCharge
  }
  charge() {
    let type = 'lighting'
    return {...this, type}
  }
}

// 購買!
let c2LAdapter = new C2LAdapter(typec_charger);
// 拿出我的 IPhone 11 Pro Max 充充看!
iPhone11ProMax.getCharge(c2LAdapter.charge());

​ 接著我們如願以償地使用介面卡 為我們的IPhone11Pro充上了電

IPhoneIPhone 11 Pro Max正在充電, 當前電量1%
VM1584:39 IPhoneIPhone 11 Pro Max正在充電, 當前電量2%
VM1584:39 IPhoneIPhone 11 Pro Max正在充電, 當前電量3%
VM1584:39 IPhoneIPhone 11 Pro Max正在充電, 當前電量4%
                                        ...
VM1584:39 IPhoneIPhone 11 Pro Max正在充電, 當前電量98%
VM1584:39 IPhoneIPhone 11 Pro Max正在充電, 當前電量99%
VM1584:39 IPhoneIPhone 11 Pro Max正在充電, 當前電量100%
VM1584:35 充電完畢,請移除電源

​ OK 本著節約用電和保護電池的原則,讓我們拔掉充電器

iPhone11ProMax.removeCharger()

// 電源已移除, 當前電量100

​ 至此, 我們使用一個介面卡完美地解決了使用Type-C充電器為我的IPhone11ProMax充電的難題。

但還不夠完美!

​ 我們的CtoL介面卡能夠實現C到L口的轉換,但如果此時我如果只有一個安卓手機和一根Lighting資料線怎麼辦?

​ 所以,我們的介面卡需要更加通用!

​ 我們輕輕地對我們的介面卡進行一些改造:

// 萬能充電介面卡(萬能充???)
class AlmightyAdapter {
  constructor() {
    this.outputType = null;
       this.outputCharger = null;
       this.inputCharger = null;
  }
    // 執行轉換
  typeChange() {
    let { startCharge } = this.inputCharger;
        this.outputCharger = {
            startCharge;
      type: this.outputType
        }
  }
  // 輸入的充電器, 希望輸出的型別
  charge(inputCharger, outputType) {
        this.inputCharger = inputCharger;
    this.outputType = outputType;
        this.typeChange();
    return this.outputCharger
    }
}

​ 買一個試試看

let almightyAdapter = new AlmightyAdapter();
// 拿出我的 IPhone 11 Pro Max 充充看!
iPhone11ProMax.getCharge(almightyAdapter.charge(typec_charger, 'lighting'));

也許還能更好!

almightyAdapter.charge( [輸入的充電器], [期望輸出的型別] )

​ almightyAdapter.charge方法接受的第二個引數是我們期望輸出的充電介面型別,往往我們可能不知道IPhone11ProMax的充電模式是什麼[模式自動識別]。我們能否實現接入手機時自動是否期望輸出的介面型別呢?

​ 也就是說能否實現這樣的呼叫方式呢?

// 介面卡的almightyAdapter.charge方法只顯式接受一個輸入充電器, 自動獲取受電裝置的型別,決定自己的輸出
iPhone11ProMax.getCharge(almightyAdapter.charge(typec_charger));
// 一個天才萬能介面卡
class GeniusAlmightyAdapter {
  constructor() {
    this.outputType = null;
       this.outputCharger = null;
       this.inputCharger = null;
  }
  // 增加一個試探接入的充電裝置Usb模式的方法
  // 該方法改變例項的輸出型別後放回自身
  tryToTestPhoneType(phone) {
    this.outputType = phone.usbType;
    return this;
  }
    // 執行轉換
  typeChange() {
    let { startCharge } = this.inputCharger;
        this.outputCharger = {
            startCharge;
      type: this.outputType
        }
  }
  // 輸入的充電器, 希望輸出的型別
  charge(inputCharger) {
        this.inputCharger = inputCharger;
        this.typeChange();
    return this.outputCharger
    }
}

​ 如何使用?

// 如下使用
iPhone11ProMax.getCharge(almightyAdapter.tryToTestPhoneType(iPhone11ProMax).charge(typec_charger));

​ 拆解一下

// 購買一個天才萬能充電器
const almightyAdapter = new AlmightyAdapter;
// 得到應輸出模式的天才萬能充電器
const adapterHasOutputType =  almightyAdapter.tryToTestPhoneType(iPhone11ProMax);
充電開始
iPhone11ProMax.getCharge(adapterHasOutputType.charge(typec_charger));

介面卡模式為我們解決了什麼問題

​ 它抹平了一些系統或者包在接入其他系統時產生的資料格式、模式轉換問題,讓我們在開發時更加專注於邏輯程式碼的開發,使一些可能產生的資料異常在系統外隔離,增強系統的健壯性,提升系統的純粹性,避免大量的資料處理和邏輯纏繞在一起,另外接入或被接入方在更新迭代後,提供或預期的資料/介面模式可能會發生一些變化,而我們只需在介面卡這一層做一些相容處理保持輸入輸出的穩定即可。

註釋

[模式自動識別]:我們只知道希望得到IPhone11ProMax的充電模式,我們可能不知道透過IPhone11ProMax.usbType獲取,我們更希望這樣的邏輯透過介面卡自動讀取,實際上現在的手機充電器往往在接入裝置後,會去主動嘗試獲取裝置的充電協議,比如高通的QC3.0、Apple使用的DP等,這些步驟完全是自動的,我們沒有手動配置介面卡,是介面卡在接入裝置後自動完成了這個檢測的工作

相關文章