问题的提出 

定义:现在我们要开发一个应用,模拟移动存储设备的读写,即计算机与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方法留空或抛出异常,也可以不要这两个接口。总之一句话:理论是死的,人是活的,一切从现实需要来,防止设计不足,也要防止设计过度。
在这里,我们姑且认为以后的移动存储都是能读又能写的,所以我们选方案三。
实现

下面,我们要将解决方案加以实现。我选择的语言是C#,但是在代码中不会用到C#特有的性质,所以使用其他语言的朋友一样可以参考。
首先编写IMobileStorage接口:
Code:IMobileStorage
1面向接口编程详解(二)——编程实例(1)_java  面向接口编程  接口编程 jnamespace InterfaceExample
2面向接口编程详解(二)——编程实例(1)_java  面向接口编程  接口编程 j_02{
3面向接口编程详解(二)——编程实例(1)_职场_05    public interface IMobileStorage
4面向接口编程详解(二)——编程实例(1)_java  面向接口编程  接口编程 j_06    {
5面向接口编程详解(二)——编程实例(1)_职场_05        void Read();//从自身读数据
6面向接口编程详解(二)——编程实例(1)_职场_05        void Write();//将数据写入自身
7面向接口编程详解(二)——编程实例(1)_职场_11    }

8面向接口编程详解(二)——编程实例(1)_职场_12}
 
代码比较简单,只有两个方法,没什么好说的,接下来是三个移动存储设备的具体实现代码:
U盘
Code:FlashDisk
 1面向接口编程详解(二)——编程实例(1)_java  面向接口编程  接口编程 jnamespace InterfaceExample
 2面向接口编程详解(二)——编程实例(1)_休闲_14{
 3面向接口编程详解(二)——编程实例(1)_职场_05    public class FlashDisk : IMobileStorage
 4面向接口编程详解(二)——编程实例(1)_职场_18    {
 5面向接口编程详解(二)——编程实例(1)_职场_05        public void Read()
 6面向接口编程详解(二)——编程实例(1)_职场_22        {
 7面向接口编程详解(二)——编程实例(1)_职场_05            Console.WriteLine("Reading from FlashDisk……");
 8面向接口编程详解(二)——编程实例(1)_职场_05            Console.WriteLine("Read finished!");
 9面向接口编程详解(二)——编程实例(1)_职场_11        }

10面向接口编程详解(二)——编程实例(1)_职场_05
11面向接口编程详解(二)——编程实例(1)_职场_05        public void Write()
12面向接口编程详解(二)——编程实例(1)_java  面向接口编程  接口编程 j_30        {
13面向接口编程详解(二)——编程实例(1)_职场_05            Console.WriteLine("Writing to FlashDisk……");
14面向接口编程详解(二)——编程实例(1)_职场_05            Console.WriteLine("Write finished!");
15面向接口编程详解(二)——编程实例(1)_职场_11        }

16面向接口编程详解(二)——编程实例(1)_职场_11    }

17面向接口编程详解(二)——编程实例(1)_职场_12}

MP3
Code:MP3Player
 1面向接口编程详解(二)——编程实例(1)_java  面向接口编程  接口编程 jnamespace InterfaceExample
 2面向接口编程详解(二)——编程实例(1)_java  面向接口编程  接口编程 j_39{
 3面向接口编程详解(二)——编程实例(1)_职场_05    public class MP3Player : IMobileStorage
 4面向接口编程详解(二)——编程实例(1)_java  面向接口编程  接口编程 j_43    {
 5面向接口编程详解(二)——编程实例(1)_职场_05        public void Read()
 6面向接口编程详解(二)——编程实例(1)_职场_22        {
 7面向接口编程详解(二)——编程实例(1)_职场_05            Console.WriteLine("Reading from MP3Player……");
 8面向接口编程详解(二)——编程实例(1)_职场_05            Console.WriteLine("Read finished!");
 9面向接口编程详解(二)——编程实例(1)_职场_11        }

10面向接口编程详解(二)——编程实例(1)_职场_05
11面向接口编程详解(二)——编程实例(1)_职场_05        public void Write()
12面向接口编程详解(二)——编程实例(1)_java  面向接口编程  接口编程 j_30        {
13面向接口编程详解(二)——编程实例(1)_职场_05            Console.WriteLine("Writing to MP3Player……");
14面向接口编程详解(二)——编程实例(1)_职场_05            Console.WriteLine("Write finished!");
15面向接口编程详解(二)——编程实例(1)_职场_11        }

16面向接口编程详解(二)——编程实例(1)_职场_05
17面向接口编程详解(二)——编程实例(1)_职场_05        public void PlayMusic()
18面向接口编程详解(二)——编程实例(1)_java  面向接口编程  接口编程 j_63        {
19面向接口编程详解(二)——编程实例(1)_职场_05            Console.WriteLine("Music is playing……");
20面向接口编程详解(二)——编程实例(1)_职场_11        }

21面向接口编程详解(二)——编程实例(1)_职场_11    }

22面向接口编程详解(二)——编程实例(1)_职场_12}

移动硬盘
Code:MobileHardDisk
 1面向接口编程详解(二)——编程实例(1)_java  面向接口编程  接口编程 jnamespace InterfaceExample
 2面向接口编程详解(二)——编程实例(1)_java  面向接口编程  接口编程 j_71{
 3面向接口编程详解(二)——编程实例(1)_职场_05    public class MobileHardDisk : IMobileStorage
 4面向接口编程详解(二)——编程实例(1)_休闲_75    {
 5面向接口编程详解(二)——编程实例(1)_职场_05        public void Read()
 6面向接口编程详解(二)——编程实例(1)_java  面向接口编程  接口编程 j_79        {
 7面向接口编程详解(二)——编程实例(1)_职场_05            Console.WriteLine("Reading from MobileHardDisk……");
 8面向接口编程详解(二)——编程实例(1)_职场_05            Console.WriteLine("Read finished!");
 9面向接口编程详解(二)——编程实例(1)_职场_11        }

10面向接口编程详解(二)——编程实例(1)_职场_05
11面向接口编程详解(二)——编程实例(1)_职场_05        public void Write()
12面向接口编程详解(二)——编程实例(1)_休闲_87        {
13面向接口编程详解(二)——编程实例(1)_职场_05            Console.WriteLine("Writing to MobileHardDisk……");
14面向接口编程详解(二)——编程实例(1)_职场_05            Console.WriteLine("Write finished!");
15面向接口编程详解(二)——编程实例(1)_职场_11        }

16面向接口编程详解(二)——编程实例(1)_职场_11    }

17面向接口编程详解(二)——编程实例(1)_职场_12}

可以看到,它们都实现了IMobileStorage接口,并重写了各自不同的Read和Write方法。下面,我们来写Computer:
Code:Computer
 1面向接口编程详解(二)——编程实例(1)_java  面向接口编程  接口编程 jnamespace InterfaceExample
 2面向接口编程详解(二)——编程实例(1)_java  面向接口编程  接口编程 j_96{
 3面向接口编程详解(二)——编程实例(1)_职场_05    public class Computer
 4面向接口编程详解(二)——编程实例(1)_职场_100    {
 5面向接口编程详解(二)——编程实例(1)_职场_05        private IMobileStorage _usbDrive;
 6面向接口编程详解(二)——编程实例(1)_职场_05
 7面向接口编程详解(二)——编程实例(1)_职场_05        public IMobileStorage UsbDrive
 8面向接口编程详解(二)——编程实例(1)_休闲_106        {
 9面向接口编程详解(二)——编程实例(1)_职场_05            get
10面向接口编程详解(二)——编程实例(1)_java  面向接口编程  接口编程 j_110            {
11面向接口编程详解(二)——编程实例(1)_职场_05                return this._usbDrive;
12面向接口编程详解(二)——编程实例(1)_职场_11            }

13面向接口编程详解(二)——编程实例(1)_职场_05            set
14面向接口编程详解(二)——编程实例(1)_java  面向接口编程  接口编程 j_116            {
15面向接口编程详解(二)——编程实例(1)_职场_05                this._usbDrive = value;
16面向接口编程详解(二)——编程实例(1)_职场_11            }

17面向接口编程详解(二)——编程实例(1)_职场_11        }

18面向接口编程详解(二)——编程实例(1)_职场_05
19面向接口编程详解(二)——编程实例(1)_职场_05        public Computer()
20面向接口编程详解(二)——编程实例(1)_java  面向接口编程  接口编程 j_124        {
21面向接口编程详解(二)——编程实例(1)_职场_11        }

22面向接口编程详解(二)——编程实例(1)_职场_05
23面向接口编程详解(二)——编程实例(1)_职场_05        public Computer(IMobileStorage usbDrive)
24面向接口编程详解(二)——编程实例(1)_java  面向接口编程  接口编程 j_130        {
25面向接口编程详解(二)——编程实例(1)_职场_05            this.UsbDrive = usbDrive;
26面向接口编程详解(二)——编程实例(1)_职场_11        }

27面向接口编程详解(二)——编程实例(1)_职场_05    
28面向接口编程详解(二)——编程实例(1)_职场_05        public void ReadData()
29面向接口编程详解(二)——编程实例(1)_java  面向接口编程  接口编程 j_137        {
30面向接口编程详解(二)——编程实例(1)_职场_05            this._usbDrive.Read();
31面向接口编程详解(二)——编程实例(1)_职场_11        }

32面向接口编程详解(二)——编程实例(1)_职场_05
33面向接口编程详解(二)——编程实例(1)_职场_05        public void WriteData()
34面向接口编程详解(二)——编程实例(1)_java  面向接口编程  接口编程 j_144        {
35面向接口编程详解(二)——编程实例(1)_职场_05            this._usbDrive.Write();
36面向接口编程详解(二)——编程实例(1)_职场_11        }

37面向接口编程详解(二)——编程实例(1)_职场_11    }

38面向接口编程详解(二)——编程实例(1)_职场_12}