天天看點

java基礎——面向接口程式設計詳解(二)程式設計執行個體

問題的提出

定義:現在我們要開發一個應用,模拟移動儲存設備的讀寫,即計算機與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再繼承它們。這樣做,顯然進一步提高了靈活性,但是,這有沒有設計過度的嫌疑呢?我的觀點是:這要看具體情況。如果我們的應用中可能會出現一些類,這些類隻實作讀方法或隻實作寫方法,如隻讀CD光牒,那麼這樣做也是可以的。如果我們知道以後出現的東西都是能讀又能寫的,那這兩個接口就沒有必要了。其實如果将隻讀裝置的Write方法留白或抛出異常,也可以不要這兩個接口。總之一句話:理論是死的,人是活的,一切從現實需要來,防止設計不足,也要防止設計過度。

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

實作

  下面,我們要将解決方案加以實作。我選擇的語言是C#,但是在代碼中不會用到C#特有的性質,是以使用其他語言的朋友一樣可以參考。

  首先編寫IMobileStorage接口:

public interface MobileStorage{
       void Read();//從自身讀資料
       void Write();//将資料寫入自身
   }      

代碼比較簡單,隻有兩個方法,沒什麼好說的,接下來是三個移動儲存設備的具體實作代碼:

  U盤

public class FlashDisk implements MobileStorage{
        //實作接口 實作接口中的方法
        public void Read(){
            Console.WriteLine("Reading from FlashDisk……");
            Console.WriteLine("Read finished!");
        }
        public void Write(){
            Console.WriteLine("Writing to FlashDisk……");
            Console.WriteLine("Write finished!");
        }
    }      

MP3

public class MP3Player implements MobileStorage{
        public void Read(){
            Console.WriteLine("Reading from MP3Player……");
            Console.WriteLine("Read finished!");
        }
        public void Write(){
            Console.WriteLine("Writing to MP3Player……");
            Console.WriteLine("Write finished!");
        }
        public void PlayMusic(){
            Console.WriteLine("Music is playing……");
        }
    }      

移動硬碟

public class MobileHardDisk implements MobileStorage{
        public void Read(){
            Console.WriteLine("Reading from MobileHardDisk……");
            Console.WriteLine("Read finished!");
        }
        public void Write(){
            Console.WriteLine("Writing to MobileHardDisk……");
            Console.WriteLine("Write finished!");
        }
    }      

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

public class Computer{
       private MobileStorage usbDrive;
       public MobileStorage getUsbDrive{
             return this.usbDrive;
       }
       public void setUserDriver(MobileStorage usbDrive){
            this.usbDrive = usbDrive;
       }
       public Computer(){
       }
       public Computer(MobileStorage usbDrive){
           this.UsbDrive = usbDrive;
       }
       public void ReadData(){
           this.usbDrive.Read();
       }
       public void WriteData(){
           this.usbDrive.Write();
       }
   }      

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

  OK!下面我們來測試我們的“電腦”和“移動儲存設備”是否工作正常。我是用的C#控制台程式,具體代碼如下:

class Tester{
        public static void Main(string[] args)
        {
            Computer computer = new Computer();
            MobileStorage mp3Player = new MP3Player();//多态 一個接口 不同的實作
            MobileStorage flashDisk = new FlashDisk();
            MobileStorage mobileHardDisk = new MobileHardDisk();
            System.out.println("I inserted my MP3 Player into my computer and copy some music to it:");
            computer.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.UsbDrive = mobileHardDisk;
            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.UsbDrive = flashDisk;
            computer.ReadData();
            computer.WriteData();
        }
    }      

現在編譯、運作程式,如果沒有問題,将看到如下運作結果:

java基礎——面向接口程式設計詳解(二)程式設計執行個體

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

後來……

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

class Tester{
        //對拓展開放 對修改關閉
        public static void Main(string[] args){
            Computer computer = new Computer();
            MobileStorage newMobileStorage = new NewMobileStorage();
            System.out.println("Now,I am testing the new mobile storage:");
            computer.UsbDrive = newMobileStorage;
            computer.ReadData();
            computer.WriteData();
        }
    }      

編譯、運作、看結果: