面向接口编程(二)

通过上一篇文章的讨论,我想各位朋友对“面接接口编程”有了一个大致的了解。那么在这一篇里,我们用一个例子,让各位对这个重要的编程思想有个直观的印象。为充分考虑到初学者,所以这个例子非常简单,望各位高手见谅。

问题的提出  


定义: 现在我们要开发一个应用,模拟移动存储设备的读写,即计算机与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 namespace  InterfaceExample
2 {
3      public   interface  IMobileStorage
4      {
5          void  Read(); // 从自身读数据
6          void  Write(); // 将数据写入自身
7     }

8 }

 

代码比较简单,只有两个方法,没什么好说的,接下来是三个移动存储设备的具体实现代码:

U盘

Code:FlashDisk

 1 namespace  InterfaceExample
 2 {
 3      public   class  FlashDisk : IMobileStorage
 4      {
 5          public   void  Read()
 6          {
 7             Console.WriteLine( " Reading from FlashDisk…… " );
 8             Console.WriteLine( " Read finished! " );
 9         }

10
11          public   void  Write()
12          {
13             Console.WriteLine( " Writing to FlashDisk…… " );
14             Console.WriteLine( " Write finished! " );
15         }

16     }

17 }


MP3

Code:MP3Player

 1 namespace  InterfaceExample
 2 {
 3      public   class  MP3Player : IMobileStorage
 4      {
 5          public   void  Read()
 6          {
 7             Console.WriteLine( " Reading from MP3Player…… " );
 8             Console.WriteLine( " Read finished! " );
 9         }

10
11          public   void  Write()
12          {
13             Console.WriteLine( " Writing to MP3Player…… " );
14             Console.WriteLine( " Write finished! " );
15         }

16
17          public   void  PlayMusic()
18          {
19             Console.WriteLine( " Music is playing…… " );
20         }

21     }

22 }


移动硬盘

Code:MobileHardDisk

 1 namespace  InterfaceExample
 2 {
 3      public   class  MobileHardDisk : IMobileStorage
 4      {
 5          public   void  Read()
 6          {
 7             Console.WriteLine( " Reading from MobileHardDisk…… " );
 8             Console.WriteLine( " Read finished! " );
 9         }

10
11          public   void  Write()
12          {
13             Console.WriteLine( " Writing to MobileHardDisk…… " );
14             Console.WriteLine( " Write finished! " );
15         }

16     }

17 }


可以看到,它们都实现了IMobileStorage接口,并重写了各自不同的Read和Write方法。下面,我们来写Computer:

Code:Computer

 1 namespace  InterfaceExample
 2 {
 3      public   class  Computer
 4      {
 5          private  IMobileStorage _usbDrive;
 6
 7          public  IMobileStorage UsbDrive
 8          {
 9              get
10              {
11                  return   this ._usbDrive;
12             }

13              set
14              {
15                  this ._usbDrive  =  value;
16             }

17         }

18
19          public  Computer()
20          {
21         }

22
23          public  Computer(IMobileStorage usbDrive)
24          {
25              this .UsbDrive  =  usbDrive;
26         }

27     
28          public   void  ReadData()
29          {
30              this ._usbDrive.Read();
31         }

32
33          public   void  WriteData()
34          {
35              this ._usbDrive.Write();
36         }

37     }

38 }


其中的UsbDrive就是可替换的移动存储设备,之所以用这个名字,是为了让大家觉得直观,就像我们平常使用电脑上的USB插口插拔设备一样。

OK!下面我们来测试我们的“电脑”和“移动存储设备”是否工作正常。我是用的C#控制台程序,具体代码如下:

Code:测试代码

 1 namespace  InterfaceExample
 2 {
 3      class  Program
 4      {
 5          static   void  Main( string [] args)
 6          {
 7             Computer computer  =   new  Computer();
 8             IMobileStorage mp3Player  =   new  MP3Player();
 9             IMobileStorage flashDisk  =   new  FlashDisk();
10             IMobileStorage mobileHardDisk  =   new  MobileHardDisk();
11
12             Console.WriteLine( " I inserted my MP3 Player into my computer and copy some music to it: " );
13             computer.UsbDrive  =  mp3Player;
14             computer.WriteData();
15             Console.WriteLine();
16
17             Console.WriteLine( " Well,I also want to copy a great movie to my computer from a mobile hard disk: " );
18             computer.UsbDrive  =  mobileHardDisk;
19             computer.ReadData();
20             Console.WriteLine();
21
22             Console.WriteLine( " OK!I have to read some files from my flash disk and copy another file to it: " );
23             computer.UsbDrive  =  flashDisk;
24             computer.ReadData();
25             computer.WriteData();
26             Console.ReadLine();
27         }

28     }

29 }


现在编译、运行程序,如果没有问题,将看到如下运行结果:

图2.1 各种移动存储设备测试结果

好的,看来我们的系统工作良好。

后来……


 

刚过了一个星期,就有人送来了新的移动存储设备NewMobileStorage,让我测试能不能用,我微微一笑,心想这不是小菜一碟,让我们看看面向接口编程的威力吧!将测试程序修改成如下:

Code:测试代码

 1 namespace  InterfaceExample
 2 {
 3      class  Program
 4      {
 5          static   void  Main( string [] args)
 6          {
 7             Computer computer  =   new  Computer();
 8             IMobileStorage newMobileStorage  =   new  NewMobileStorage();
 9
10             Console.WriteLine( " Now,I am testing the new mobile storage: " );
11             computer.UsbDrive  =  newMobileStorage;
12             computer.ReadData();
13             computer.WriteData();
14             Console.ReadLine();
15         }

16     }

17 }


编译、运行、看结果:

哈哈,神奇吧,Computer一点都不用改动,就可以使新的设备正常运行。这就是所谓“对扩展开放,对修改关闭”。

图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

图2.3 Adapter模式应用示意

具体代码如下:

Code:SuperStorageAdapter

 1 namespace  InterfaceExample
 2 {
 3      public   class  SuperStorageAdapter : IMobileStorage
 4      {
 5          private  SuperStorage _superStorage;
 6
 7          public  SuperStorage SuperStorage
 8          {
 9              get
10              {
11                  return   this ._superStorage;
12             }

13              set
14              {
15                  this ._superStorage  =  value;
16             }

17         }

18     
19          public   void  Read()
20          {
21              this ._superStorage.rd();
22         }

23
24          public   void  Write()
25          {
26              this ._superStorage.wt();
27         }

28     }

29 }


好,现在我们来测试适配过的新设备,测试代码如下:

Code:测试代码

 1 namespace  InterfaceExample
 2 {
 3      class  Program
 4      {
 5          static   void  Main( string [] args)
 6          {
 7             Computer computer  =   new  Computer();
 8             SuperStorageAdapter superStorageAdapter  =   new  SuperStorageAdapter();
 9             SuperStorage superStorage  =   new  SuperStorage();
10             superStorageAdapter.SuperStorage  =  superStorage;
11
12             Console.WriteLine( " Now,I am testing the new super storage with adapter: " );
13             computer.UsbDrive  =  superStorageAdapter;
14             computer.ReadData();
15             computer.WriteData();
16             Console.ReadLine();
17         }

18     }

19 }


运行后会得到如下结果:

图2.4 利用Adapter模式运行新设备测试结果

OK!虽然遇到了一些困难,不过在设计模式的帮助下,我们还是在没有修改Computer任何代码的情况下实现了新设备的运行。

 好了,理论在第一篇讲得足够多了,所以这里我就不多讲了。希望各位朋友结合第一篇的理论和这个例子,仔细思考面向接口的问题。当然,不要忘了结合现实。

下一篇,我将解析经典设计模式中的面向接口编程思想和.NET平台分层架构中接口的运用。

作者: T2噬菌体
出处: http://leoo2sk.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值