天天看點

寂然解讀設計模式 - 擴充卡模式

I walk very slowly, but I never walk backwards            

設計模式 - 适配者模式

寂然

大家好,我是寂然,接下來,我們開啟第四篇章 - 結構型模式的學習,本節課,我們開啟來聊結構型模式的第一種擴充卡模式,閑言少叙,我們先來看個生活中的擴充卡案例

生活中的擴充卡案例

我們先來看個生活中的案例,世界上的用電插頭存在着多種标準,中國是三個扁頭,澳洲也在使用這種标準的插頭,而美國的插頭就是一圓兩扁,是以如果我們去美國旅遊的話,國内的插頭肯定沒辦法使用了,我們一般都會購買多功能轉接插頭,做一個轉換,來适配美國的插頭标準,那其實多功能轉接插頭就扮演着擴充卡的角色

寂然解讀設計模式 - 擴充卡模式

基本介紹

擴充卡模式(Adapter Pattern)将某個類的接口轉換成用戶端期望的另一個接口表示,主的目的是相容性,讓原本因接口不比對不能一起工作的兩個類可以協同工作,其别名為包裝器(Wrapper)

擴充卡分類

擴充卡模式屬于結構型模式,主要分為三類:

  • 類擴充卡模式
  • 對象擴充卡模式
  • 接口擴充卡模式

工作原理

擴充卡模式:将一個類的接口轉換成另一種接口,讓原本接口不相容的類可以相容

Adapter 擴充卡設計模式中有 3 個重要角色:

被适配者 Adaptee,擴充卡 Adapter 和目标對象 Target

  • 從使用者的角度看不到被适配者,是解耦的
  • 使用者調用擴充卡轉化出來的目标對象方法,擴充卡再調用被适配者的相關接口方法
  • 使用者收到回報結果,感覺隻是和目标對象互動 ,類圖如下所示
    • *
寂然解讀設計模式 - 擴充卡模式

類擴充卡

基本思路:Adapter 類,通過繼承Adaptee類,實作 Target 類接口,完成 Apdaptee -> Target 的适配

案例示範 - 電壓問題

舉個較為簡單和合适的案例,我們經常使用的手機或電腦充電器,也屬于擴充卡,它将220V的交流電(AC)轉換為手機可用的5V直流電(DC),那AC也就相當于被适配者,DC相當于目标對象,充電器扮演了擴充卡的角色,OK,那我們以手機充電器為例,示範類擴充卡模式

//被适配者:220v交流電
public class AC {
​
 public int outputAC() {
​
 int srcV = 220; //
​
 System.out.println( srcV + "V交流電");
​
 return srcV;
 }
}
​
//目标對象:5V直流電
public interface DC {
​
 int outputDC();
}
​
//擴充卡:手機充電器
public class IphoneAdapter extends AC implements DC{
​
 @Override
 public int outputDC() {
​
 //擷取到220V交流電
 int srcV = outputAC();
​
 //模拟擴充卡轉化過程,轉化為5V直流電
 int targetV = srcV / 44;
​
​
 return targetV;
 }
}
​
//手機
public class Iphone {
​
 //手機充電方法
 public void charging(DC dc){
​
 if (dc.outputDC() == 5){ //轉換成功
​
 System.out.println("電壓為5v,手機正在充電中");
 } else {
​
 System.out.println("注意安全.....");
 }
​
 }
}
​
//用戶端
public class Client {
​
 public static void main(String[] args) {
​
 new Iphone().charging(new IphoneAdapter());
​
 }
}           

可以看到,類擴充卡是通過繼承Adaptee 類,實作 Target 接口的方式實作适配的,由于他繼承了Adaptee 類,是以他可以根據需求重寫 Adaptee 類的方法,使得擴充卡的靈活性增強了,但是,由于Java單繼承的機制,這就要求目标對象 Target必須是接口,有一定的局限性

對象擴充卡

基本思路:和類擴充卡模式相同,但是對 Adapter 類做修改,不去繼承被适配者Adaptee,而是持有Adaptee類的執行個體,實作 Target 類接口,完成 Apdaptee -> Target 的适配,以解決相容性的問題

  • 根據合成複用原則,在系統中盡量使用關聯關系來替代繼承關系
  • 對象擴充卡是擴充卡模式中常用的一種

同樣,我們用上面電壓問題的案例,來示範對象擴充卡模式的使用

//被适配者:220V交流電
public class AC {
​
 public int outputAC() {
​
 int srcV = 220; //
​
 System.out.println( srcV + "V交流電");
​
 return srcV;
 }
}
​
//目标對象:5V直流電
public interface DC {
​
 int outputDC();
}
​
//擴充卡:手機充電器
public class IphoneAdapter implements DC {
​
 private AC ac; //持有被适配者對象,而不再是繼承
​
 public IphoneAdapter(AC ac){ //構造器傳值

 this.ac = ac;
 }
​
 @Override
 public int outputDC() {
​
 //擷取到220V交流電
 int srcV = this.ac.outputAC();
​
 //模拟擴充卡轉化過程,轉化為5V直流電
 int targetV = srcV / 44;
​
​
 return targetV;
 }
}
//手機
public class Iphone {
​
 //手機充電方法
 public void charging(DC dc){
​
 if (dc.outputDC() == 5){ //轉換成功
​
 System.out.println("電壓為5v,手機正在充電中");
 } else {
​
 System.out.println("注意安全.....");
 }
​
 }
}
//用戶端
public class Client {
​
 public static void main(String[] args) {
​
 new Iphone().charging(new IphoneAdapter(new AC()));
​
 }
}           

對象擴充卡和類擴充卡其實算是同一種思想,隻不過實作方式針對類擴充卡的局限性進行了優化,根據合成複用原則,用關聯關系替代了繼承,不再要求目标對象 Target 必須是接口,使用成本更低,更加靈活

接口擴充卡

一些書籍也把接口擴充卡模式稱之為預設擴充卡模式,他的基本思想是這樣的:

當不需要全部實作接口提供的方法時,可以先設計一個抽象類實作接口,并為該接口中每個方法提供一個預設實作,即(空方法),那麼該抽象類的子類可以有選擇的覆寫父類的某些方法來實作需求

适用于對于一個接口,不需要使用其所有方法的情況

案例改進 - 電壓問題

為了示範擴充卡模式,我們對上面電壓問題的案例進行簡易更新,假設現在我的目标接口有多個方法,可以輸出5V,12V,20V的電壓,按照正常邏輯,設計一個擴充卡去實作這個接口,顯然,需要實作該目标接口的所有方法,但是,現在我們實際情況中,隻需要使用其中的5V直流電給手機進行充電,不需要使用這個接口的所有方法

是以,可以設計一個中間類去把目标接口的所有方法空實作,然後擴充卡類再去繼承這個中間類,選擇性重寫我所需要的方法,不久OK了嘛,代碼示例如下圖所示:

//被适配者:220V交流電
public class AC {
​
 public int outputAC() {
​
 int srcV = 220; //
​
 System.out.println( srcV + "V交流電");
​
 return srcV;
 }
}
​
//目标接口,有多個方法
public interface IDC {
​
 int output5V();
​
 int output12V();
​
 int output20V();
}
​
//中間的抽象類,空實作所有方法
public class DefaultAdapter implements IDC{
​
 @Override
 public int output5V() {
 return 0;
 }
​
 @Override
 public int output12V() {
 return 0;
 }
​
 @Override
 public int output20V() {
 return 0;
 }
}
​
//手機充電器隻需要實作5V的方法即可,其他的沒必要
public class IphoneAdapter extends DefaultAdapter {
​
 private AC ac;
​
 public IphoneAdapter(AC ac) {
 this.ac = ac;
 }
​
 @Override
 public int output5V() { //選擇性重寫
​
 //拿到220V的交流電
 int srcV = this.ac.outputAC();
​
 //模拟擴充卡轉化過程,轉化為5V直流電
 int targetV = srcV / 44;
​
 return targetV;
​
 }
​
 public static void main(String[] args) {
​
 IphoneAdapter iphoneAdapter = new IphoneAdapter(new AC());
​
 System.out.println("手機充電器的電壓為 " + iphoneAdapter.output5V());
​
 }
}           

實作了案例需求,大家回頭再看接口擴充卡模式的基本思想,和案例需求相結合,對他的了解會更深入一些

JDK IO源碼分析

在java jdk中,擴充卡模式使用場景很多,例如java.io.InputStreamReader、java.io.OutputStreamWriter 等,我們通過一個簡單的Demo,來分析下 InputStreamReader 中的擴充卡模式

/**
 * @Classname InputStreamReaderDemo
 * @Created by 寂然
 * @Description InputStreamReader位元組輸入流轉化為字元輸入流
 */
public class InputStreamReaderDemo {
​
 public static void main(String[] args) throws IOException {
 //位元組輸入流
 InputStream is = System.in ;
​
 Reader r = new InputStreamReader(is) ;
​
 FileWriter fw = new FileWriter("d.txt") ;
​
 char[] chs = new char[1024] ;
​
 int len ;
​
 while((len = r.read(chs))!=-1){
​
 fw.write(chs, 0, len);
​
 fw.flush();
 }
​
 r.close();
​
 fw.close();
 }
}           

下面我們通過 源碼來進行分析

* @see java.nio.charset.Charset
 *
 * @author      Mark Reinhold
 * @since       JDK1.1
 */
​
public class InputStreamReader extends Reader {
​
 private final StreamDecoder sd;
​
 /**
 * Creates an InputStreamReader that uses the default charset.
 *
 * @param  in   An InputStream
 */
 //構造函數,傳入InputStream 對象in,并且關聯在StreamDecoder sd對象中
 public InputStreamReader(InputStream in) {
 super(in);
 try {
 sd = StreamDecoder.forInputStreamReader(in, this, (String)null); // ## check lock object
 } catch (UnsupportedEncodingException e) {
 // The default encoding should always be available
 throw new Error(e);
 }
 }

 //适配Reader類中字元讀取操作,調用sd中位元組讀取轉化為字元讀取。
 public int read() throws IOException {
 return sd.read();
 }

}           

具體角色分析:

  • Reader 類對應目标對象 Target
  • InputStreamReader 類對應擴充卡 Adapter
  • InputStream 對應被适配者 Adaptee

可以看到,這裡使用了擴充卡模式,InputStreamReader 将Reader 和InputStream 适配起來,在read() 方法中适配Reader類中字元讀取操作,調用sd對象中的位元組讀取轉化為了字元讀取

下節預告

OK,到這裡,擴充卡模式的相關内容就結束了,下一節,我們開啟橋接模式的學習,希望大家能夠一起堅持下去,真正有所收獲,就像開篇那句話,我走的很慢,但是我從來不後退,哈哈,那我們下期見~