1. 程式人生 > >面向介面程式設計詳解-Java篇

面向介面程式設計詳解-Java篇

 相信看到這篇文字的人已經不需要了解什麼是介面了,我就不再過多的做介紹了,直接步入正題,介面測試如何編寫。那麼在這一篇裡,我們用一個例子,讓各位對這個重要的程式設計思想有個直觀的印象。為充分考慮到初學者,所以這個例子非常簡單,望各位高手見諒。

  為了擺脫新手的概念,我這裡也儘量不用main方法,而採用testNG編寫測試用例。

定義:現在我們要開發一個應用,模擬移動儲存裝置的讀寫,即計算機與U盤、MP3、行動硬碟等裝置進行資料交換。

上下文(環境):已知要實現U盤、MP3播放器、行動硬碟三種移動儲存裝置,要求計算機能同這三種裝置進行資料交換,並且以後可能會有新的第三方的移動儲存裝置,所以計算機必須有擴充套件性,能與目前未知而以後可能會出現的儲存裝置進行資料交換。各個儲存裝置間讀、寫的實現方法不同,U盤和行動硬碟只有這兩個方法,MP3Player還有一個PlayMusic方法。

名詞定義:資料交換={讀,寫}

解決方案列舉

方案一:分別定義FlashDisk、MP3Player、MobileHardDisk三個類,實現各自的Read和Write方法。然後在Computer類中例項化上述三個類,為每個類分別寫讀、寫方法。例如,為FlashDisk寫ReadFromFlashDisk、WriteToFlashDisk兩個方法。總共六個方法。

方案二:定義抽象類MobileStorage,在裡面寫虛方法Read和Write,三個儲存裝置繼承此抽象類,並重寫Read和Write方法。Computer類中包含一個型別為MobileStorage的成員變數,併為其編寫get/set器,這樣Computer中只需要兩個方法:ReadData和WriteData,並通過多型性實現不同移動裝置的讀寫。

方案三:與方案二基本相同,只是不定義抽象類,而是定義介面IMobileStorage,移動儲存器類實現此介面。Computer中通過依賴介面IMobileStorage實現多型性。

方案四:定義介面IReadable和IWritable,兩個介面分別只包含Read和Write,然後定義介面IMobileStorage介面繼承自IReadable和IWritable,剩下的實現與方案三相同。

下面,我們來分析一下以上四種方案:

  首先,方案一最直白,實現起來最簡單,但是它有一個致命的弱點:可擴充套件性差。或者說,不符合“開放-關閉原則”(注:意為對擴充套件開放,對修改關閉)。當將來有了第三方擴充套件移動儲存裝置時,必須對Computer進行修改。這就如在一個真實的計算機上,為每一種移動儲存裝置實現一個不同的插口、並分別有各自的驅動程式。當有了一種新的移動儲存裝置後,我們就要將計算機大卸八塊,然後增加一個新的插口,在編寫一套針對此新裝置的驅動程式。這種設計顯然不可取。

  此方案的另一個缺點在於,冗餘程式碼多。如果有100種移動儲存,那我們的Computer中豈不是要至少寫200個方法,這是不能接受的!

  再看 方案二和方案三,之所以將這兩個方案放在一起討論,是因為他們基本是一個方案(從思想層面上來說),只不過實現手段不同,一個是使用了抽象類,一個是使用了介面,而且最終達到的目的應該是一樣的。

  我們先來評價這種方案:首先它解決了程式碼冗餘的問題,因為可以動態替換移動裝置,並且都實現了共同的介面,所以不管有多少種移動裝置,只要一個Read方法和一個Write方法,多型性就幫我們解決問題了。而對第一個問題,由於可以執行時動態替換,而不必將移動儲存類硬編碼在Computer中,所以有了新的第三方裝置,完全可以替換進去執行。這就是所謂的“依賴介面,而不是依賴與具體類”,不信你看看,Computer類只有一個MobileStorage型別或IMobileStorage型別的成員變數,至於這個變數具體是什麼型別,它並不知道,這取決於我們在執行時給這個變數的賦值。如此一來,Computer和移動儲存器類的耦合度大大下降。

  那麼 這裡該選抽象類還是介面呢?還記得第一篇文章我對抽象類和介面選擇的建議嗎?看動機。這裡,我們的動機顯然是實現多型性而不是為了程式碼複用,所以當然要用介面。

  最後 我們再來看一看方案四,它和方案三很類似,只是將“可讀”和“可寫”兩個規則分別抽象成了介面,然後讓IMobileStorage再繼承它們。這樣做,顯然進一步提高了靈活性,但是,這有沒有設計過度的嫌疑呢?我的觀點是:這要看具體情況。如果我們的應用中可能會出現一些類,這些類只實現讀方法或只實現寫方法,如只讀光碟,那麼這樣做也是可以的。如果我們知道以後出現的東西都是能讀又能寫的,那這兩個介面就沒有必要了。其實如果將只讀裝置的Write方法留空或丟擲異常,也可以不要這兩個介面。總之一句話:理論是死的,人是活的,一切從現實需要來,防止設計不足,也要防止設計過度。

  在這裡,我們姑且認為以後的移動儲存都是能讀又能寫的,所以我們選方案三

 

實現

下面,我們要將解決方案加以實現。我選擇的語言是Java,所以使用其他語言的朋友一樣可以參考。

首先編寫IMobileStorage介面:

Code:IMobileStorage

public interface IMobileStorage {
 
     void Read();                    // 讀取資料
     void Write();                   // 寫入資料
 
 }

  程式碼比較簡單,只有兩個方法,沒什麼好說的,接下來是三個移動儲存裝置的具體實現程式碼:

U盤

Code:FlashDisk

 public class FlashDisk implements IMobileStorage{
     @Override
     public void Read() {
         System.out.println("Reading from FlashDisk……");
         System.out.println("Read finished!");
     }
 
     @Override
     public void Write() {
         System.out.println("Writing to FlashDisk……");
         System.out.println("Write finished!");
     }
 }

 

MP3

Code:MP3Player

public class MP3Player implements IMobileStorage{
    @Override
    public void Read() {
        System.out.println("Reading from MP3Player……");
        System.out.println("Read finished!");
    }

    @Override
    public void Write() {
        System.out.println("Writing to MP3Player……");
        System.out.println("Write finished!");
    }

    public void PlayMusic(){
        System.out.println("Music is playing……");
    }
}

行動硬碟

Code:MobileHardDisk

public class MobileHardDisk implements IMobileStorage{

    @Override
    public void Read() {
        System.out.println("Reading from MobileHardDisk……");
        System.out.println("Read finished!");
    }

    @Override
    public void Write() {
        System.out.println("Writing to MobileHardDisk……");
        System.out.println("Write finished!");
    }

}

  可以看到,它們都實現了IMobileStorage介面,並重寫了各自不同的Read和Write方法。下面,我們來寫Computer:

Code:Computer

public class Computer {
    private IMobileStorage _usbDrive;

    public IMobileStorage get_usbDrive() {
        return _usbDrive;
    }

    public void set_usbDrive(IMobileStorage _usbDrive) {
        this._usbDrive = _usbDrive;
    }

    public Computer(){}

    public Computer(IMobileStorage _usbDrive) {
        this._usbDrive = _usbDrive;
    }

    public void ReadData(){
        this._usbDrive.Read();
    }

    public void WriteData(){
        this._usbDrive.Write();
    }
}

  其中的UsbDrive就是可替換的移動儲存裝置,之所以用這個名字,是為了讓大家覺得直觀,就像我們平常使用電腦上的USB插口插拔裝置一樣。

OK!下面我們來測試我們的“電腦”和“移動儲存裝置”是否工作正常。我是用的Java控制檯程式列印結果,具體程式碼如下:

Code:測試程式碼

public class ToTest {
    @Test
    public void program1(){
        Computer computer = new Computer();
        IMobileStorage mp3Player = new MP3Player();
        IMobileStorage flashDisk = new FlashDisk();
        IMobileStorage moblieHardDisk = new MobileHardDisk();

        System.out.println("I inserted my MP3 Player into my computer and copy some music to it:");
        computer.set_usbDrive(mp3Player);
        computer.WriteData();
        System.out.println("====================");

        System.out.println("Well,I also want to copy a great movie to my computer from a mobile hard disk:");
        computer.set_usbDrive(moblieHardDisk);
        computer.ReadData();
        System.out.println("====================");

        System.out.println("OK!I have to read some files from my flash disk and copy another file to it:");
        computer.set_usbDrive(flashDisk);
        computer.ReadData();
        computer.WriteData();
        System.out.println();
    }

 

執行結果如下:

圖2.1 各種移動儲存裝置測試結果

              圖2.1 各種移動儲存裝置測試結果

 

  好的,看來我們的系統工作良好。

  後來……

 

  剛過了一個星期,就有人送來了新的移動儲存裝置NewMobileStorage,讓我測試能不能用,我微微一笑,心想這不是小菜一碟,讓我們看看面向介面程式設計的威力吧!將測試程式修改成如下:

 (NewMobileStorage的類請參照u盤、行動硬碟等類編寫……也可以自創)

測試程式碼

 @Test
    public void program2(){
        Computer computer = new Computer();
        IMobileStorage newMobileStorage = new NewMoblieStorage();
        computer.set_usbDrive(newMobileStorage);
        newMobileStorage.Write();
        newMobileStorage.Read();

    }

執行結果:

              圖2.2 新裝置擴充套件測試結果

 

  又過了幾天,有人通知我說又有一個叫SuperStorage的移動裝置要接到我們的Computer上,我心想來吧,管你是“超級儲存”還是“特級儲存”,我的“面向介面程式設計大法”把你們統統搞定。

  但是,當裝置真的送來,我傻眼了,開發這個新裝置的團隊沒有拿到我們的IMobileStorage介面,自然也沒有遵照這個約定。這個裝置的讀、寫方法不叫Read和Write,而是叫rd和wt,這下完了……不符合介面啊,插不上。但是,不要著急,我們回到現實來找找解決的辦法。我們一起想想:如果你的Computer上只有USB介面,而有人拿來一個PS/2的滑鼠要插上用,你該怎麼辦?想起來了吧,是不是有一種叫“PS/2-USB”轉換器的東西?也叫介面卡,可以進行不同介面的轉換。對了!程式中也有轉換器。

  這裡,我要引入一個設計模式,叫“Adapter”。它的作用就如現實中的介面卡一樣,把介面不一致的兩個外掛接合起來。由於本篇不是講設計模式的,而且Adapter設計模式很好理解,所以我就不細講了,先來看我設計的類圖吧:

  如圖所示,雖然SuperStorage沒有實現IMobileStorage,但我們定義了一個實現IMobileStorage的SuperStorageAdapter,它聚合了一個SuperStorage,並將rd和wt適配為Read和Write,SuperStorageAdapter(這裡注意自行編寫SuperStorage的類和他用到的介面

            圖2.3 Adapter模式應用示意

  

具體程式碼如下:

Code:SuperStorage

public class SuperStorage {
   
	public void rd(){
		System.out.println("--模擬外部特殊儲存裝置--");
		System.out.println("Reading from SuperStorage…… ");
		System.out.println(" Read finished!");
	}
	
	public void wt(){
		System.out.println(" Writing to SuperStorage……");
		System.out.println(" Write finished!");
	}
}

Code:SuperStorageAdapter

public class SuperStorageAdapter implements IMobileStorage {
    private SuperStorage _superStorage;

    public SuperStorage get_superStorage() {
        return _superStorage;
    }

    public void set_superStorage(SuperStorage _superStorage) {
        this._superStorage = _superStorage;
    }

    @Override
    public void Read(){
        this._superStorage.rd();
    }

    @Override
    public void Write() {
        this._superStorage.wt();
    }
}

好,現在我們來測試適配過的新裝置,測試程式碼如下:

Code:測試程式碼

 @Test
    public void program3(){
        Computer computer = new Computer();
        SuperStorageAdapter superStorageAdapter = new SuperStorageAdapter();
        SuperStorage superStorage = new SuperStorage();
        superStorageAdapter.set_superStorage(superStorage);

        System.out.println("Now,I am testing the new super storage with adapter:");
        computer.set_usbDrive(superStorageAdapter);
        computer.ReadData();
        computer.WriteData();
        System.out.println();
    }

執行結果:

          圖2.4 利用Adapter模式執行新裝置測試結果

 

 

  OK!雖然遇到了一些困難,不過在設計模式的幫助下,我們還是在沒有修改Computer任何程式碼的情況下實現了新裝置的執行。希望各位朋友結合第一篇的理論和這個例子,仔細思考面向介面的問題。當然,不要忘了結合現實。

    感謝:https://www.cnblogs.com/iceb/p/7093884.html