首先,方案一最直白,实现起来最简单,但是它有一个致命的弱点:可扩展性差。或者说,不符合“开放-关闭原则”(注:意为对扩展开放,对修改关闭)。当将来有了第三方扩展移动存储设备时,必须对Computer进行修改。这就如在一个真实的计算机上,为每一种移动存储设备实现一个不同的插口、并分别有各自的驱动程序。当有了一种新的移动存储设备后,我们就要将计算机大卸八块,然后增加一个新的插口,在编写一套针对此新设备的驱动程序。这种设计显然不可取。
此方案的另一个缺点在于,冗余代码多。如果有100种移动存储,那我们的Computer中岂不是要至少写200个方法,这是不能接受的!
我们再来看方案二和方案三,之所以将这两个方案放在一起讨论,是因为他们基本是一个方案(从思想层面上来说),只不过实现手段不同,一个是使用了抽象类,一个是使用了接口,而且最终达到的目的应该是一样的。
我们先来评价这种方案:首先它解决了代码冗余的问题,因为可以动态替换移动设备,并且都实现了共同的接口,所以不管有多少种移动设备,只要一个Read方法和一个Write方法,多态性就帮我们解决问题了。而对第一个问题,由于可以运行时动态替换,而不必将移动存储类硬编码在Computer中,所以有了新的第三方设备,完全可以替换进去运行。这就是所谓的“依赖接口,而不是依赖与具体类”,不信你看看,Computer类只有一个MobileStorage类型或IMobileStorage类型的成员变量,至于这个变量具体是什么类型,它并不知道,这取决于我们在运行时给这个变量的赋值。如此一来,Computer和移动存储器类的耦合度大大下降。
那么这里该选抽象类还是接口呢?看动机。这里,我们的动机显然是实现多态性而不是为了代码复用,所以当然要用接口。
最后我们再来看一看方案四,它和方案三很类似,只是将“可读”和“可写”两个规则分别抽象成了接口,然后让IMobileStorage再继承它们。这样做,显然进一步提高了灵活性,但是,这有没有设计过度的嫌疑呢?我的观点是:这要看具体情况。如果我们的应用中可能会出现一些类,这些类只实现读方法或只实现写方法,如只读光盘,那么这样做也是可以的。如果我们知道以后出现的东西都是能读又能写的,那这两个接口就没有必要了。其实如果将只读设备的Write方法留空或抛出异常,也可以不要这两个接口。总之一句话:理论是死的,人是活的,一切从现实需要来,防止设计不足,也要防止设计过度。
在这里,我们姑且认为以后的移动存储都是能读又能写的,所以我们选方案三。
首先编写IMobileStorage接口:
namespace InterfaceExample { public interface IMobileStorage { void Read();//从自身读数据 void Write();//将数据写入自身 } }
代码比较简单,只有两个方法,没什么好说的,接下来是三个移动存储设备的具体实现代码:
U盘
namespace InterfaceExample { public class FlashDisk : IMobileStorage { 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
namespace InterfaceExample { public class MP3Player : IMobileStorage { 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……"); } } }
移动硬盘
namespace InterfaceExample { public class MobileHardDisk : IMobileStorage { 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:
namespace InterfaceExample { public class Computer { private IMobileStorage _usbDrive; public IMobileStorage UsbDrive { get { return this._usbDrive; } set { this._usbDrive = value; } } public Computer() { } public Computer(IMobileStorage usbDrive) { this.UsbDrive = usbDrive; } public void ReadData() { this._usbDrive.Read(); } public void WriteData() { this._usbDrive.Write(); } } }
实现如下:
public class Adapter : IMobileStorage { private OtherStorge _otherStorage;//OtherStorge对象是不满足当前接口的外在对象或接口 public OtherStorge OtherStorage { get { return this._otherStorage; } set { this._otherStorage = value; } } public void Read() { this._otherStorage.rd();/<span style="font-family: verdana, Arial, Helvetica, sans-serif;">/不满足接口设备的读操作</span> } public void Write() { this._otherStorage.wt();//不满足接口设备的写操作 } }
static void Main(string[] args) { Computer computer = new Computer(); SuperStorageAdapter superStorageAdapter = new SuperStorageAdapter(); SuperStorage superStorage = new SuperStorage(); superStorageAdapter.SuperStorage = superStorage; Console.WriteLine("Now,I am testing the new super storage with adapter:"); computer.UsbDrive = superStorageAdapter; computer.ReadData(); computer.WriteData(); Console.ReadLine(); }