文档章节

面向接口编程详解(二)——编程实例

不最醉不龟归
 不最醉不龟归
发布于 2016/11/17 16:35
字数 3048
阅读 5
收藏 1
点赞 0
评论 0

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

问题的提出 

定义:现在我们要开发一个应用,模拟移动存储设备的读写,即计算机与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

1namespace InterfaceExample
2{
3    public interface IMobileStorage
4    {
5        void Read();//从自身读数据
6        void Write();//将数据写入自身
7    }
8}

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

U盘

Code:FlashDisk

 1namespace 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

 1namespace 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

 1namespace 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

 1namespace 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:测试代码

 1namespace 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:测试代码

 1namespace 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

 1namespace 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:测试代码

 1namespace 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任何代码的情况下实现了新设备的运行。

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

本文转载自:http://www.cnblogs.com/leoo2sk/archive/2008/04/11/1148236.html

共有 人打赏支持
不最醉不龟归
粉丝 11
博文 262
码字总数 435067
作品 0
深圳
程序员
.NET系列文章——近一年文章分类整理,方便各位博友们查询学习

由于博主今后一段时间可能会很忙(准备出书:《.NET框架设计—模式、配置、工具》,外加换了新工作),所以博客会很少更新; 在最近一年左右时间里,博主各种.NET技术类型的文章都写过,根据...

王清培 ⋅ 2014/03/02 ⋅ 0

Java入门教程

Java是一种跨平台的语言,一次编写,到处运行,在世界编程语言排行榜中稳居第二名(第一名是C语言)。 Java用途广泛,可以用来开发传统的客户端软件和网站后台,也可以开发如火如荼 Android ...

harries ⋅ 2015/01/21 ⋅ 2

API 类和面向对象简介

API(Application Programming Interface,应用程序编程接口)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工...

ITCHN ⋅ 2016/12/22 ⋅ 0

Golang 学习笔记(5)—— 面向对象编程

本文为转载,原文:Golang 学习笔记(5)—— 面向对象编程 前言 golang是面向过程的语言,go中没有类,单go支持面向对象的编程,go中的struct就像其他语言中的类;go里没有继承,但可以通过...

ChainZhang ⋅ 2017/12/25 ⋅ 0

.NET架构设计、框架设计系列文章总结

从事.NET开发到现在已经有七个年头了。慢慢的可能会很少写.NET文章了。不知不觉竟然走了这么多年,热爱.NET热爱c#。突然想对这一路的经历进行一个总结。 是时候开始下一阶段的旅途,希望这些...

王清培 ⋅ 2016/11/13 ⋅ 0

Java系列文章(全)

JVM JVM系列:类装载器的体系结构 JVM系列:Class文件检验器 JVM系列:安全管理器 JVM系列:策略文件 Java垃圾回收机制 深入剖析Classloader(一)--类的主动使用与被动使用 深入剖析Classloader(二...

www19 ⋅ 2017/07/04 ⋅ 0

这是EnterLib PIAB的BUG吗?

在默认的情况下,EnterLib的PIAB采用基于TransparentProxy/RealProxy的机制实现对方法调用的拦截,进而实现了对横切关注点(Crosscutting Concern)的动态注入。也正是其来截机制本身的局限,...

长平狐 ⋅ 2012/09/04 ⋅ 0

面向接口编程详解(一)

我想,对于各位使用面向对象编程语言的程序员来说,“接口”这个名词一定不陌生,但是不知各位有没有这样的疑惑:接口有什么用途?它和抽象类有什么区别?能不能用抽象类代替接口呢?而且,作...

不最醉不龟归 ⋅ 2016/11/17 ⋅ 0

java编程新手入门基础知识学习

Java是一种可以撰写跨平台应用软件的面向对象的程序设计语言。Java 技术具有卓越的通用性、高效性、平台移植性和安全性,广泛应用于PC、数据中心、游戏控制台、科学超级计算机、移动电话和互...

Java小辰 ⋅ 05/15 ⋅ 0

互联网技术栈 『Contents Catalog』

image.png 编程语言 Java书单——由入门到上天 UML-类间关系 Java解读-ThreadLocal详解与应用 并发编程-Concurrent用户指南 Java并发编程-原子性变量 Java 并发工具包-常用线程池 基于事件驱...

高广超 ⋅ 2017/11/03 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

tcp/ip详解-链路层

简介 设计链路层的目的: 为IP模块发送和接收IP数据报 为ARP模块发送ARP请求和接收ARP应答 为RARP模块发送RARP请求和接收RARP应答 TCP/IP支持多种链路层协议,如以太网、令牌环往、FDDI、RS-...

loda0128 ⋅ 今天 ⋅ 0

spring.net aop代码例子

https://www.cnblogs.com/haogj/archive/2011/10/12/2207916.html

whoisliang ⋅ 今天 ⋅ 0

发送短信如何限制1小时内最多发送11条短信

发送短信如何限制1小时内最多发送11条短信 场景: 发送短信属于付费业务,有时为了防止短信攻击,需要限制发送短信的频率,例如在1个小时之内最多发送11条短信. 如何实现呢? 思路有两个 截至到当...

黄威 ⋅ 昨天 ⋅ 0

mysql5.7系列修改root默认密码

操作系统为centos7 64 1、修改 /etc/my.cnf,在 [mysqld] 小节下添加一行:skip-grant-tables=1 这一行配置让 mysqld 启动时不对密码进行验证 2、重启 mysqld 服务:systemctl restart mysql...

sskill ⋅ 昨天 ⋅ 0

Intellij IDEA神器常用技巧六-Debug详解

在调试代码的时候,你的项目得debug模式启动,也就是点那个绿色的甲虫启动服务器,然后,就可以在代码里面断点调试啦。下面不要在意,这个快捷键具体是啥,因为,这个keymap是可以自己配置的...

Mkeeper ⋅ 昨天 ⋅ 0

zip压缩工具、tar打包、打包并压缩

zip 支持压缩目录 1.在/tmp/目录下创建目录(study_zip)及文件 root@yolks1 study_zip]# !treetree 11└── 2 └── 3 └── test_zip.txt2 directories, 1 file 2.yum...

蛋黄Yolks ⋅ 昨天 ⋅ 0

聊聊HystrixThreadPool

序 本文主要研究一下HystrixThreadPool HystrixThreadPool hystrix-core-1.5.12-sources.jar!/com/netflix/hystrix/HystrixThreadPool.java /** * ThreadPool used to executed {@link Hys......

go4it ⋅ 昨天 ⋅ 0

容器之上传镜像到Docker hub

Docker hub在国内可以访问,首先要创建一个账号,这个后面会用到,我是用126邮箱注册的。 1. docker login List-1 Username不能使用你注册的邮箱,要用使用注册时用的username;要输入密码 ...

汉斯-冯-拉特 ⋅ 昨天 ⋅ 0

SpringBoot简单使用ehcache

1,SpringBoot版本 2.0.3.RELEASE ①,pom.xml <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.3.RELE......

暗中观察 ⋅ 昨天 ⋅ 0

Spring源码解析(八)——实例创建(下)

前言 来到实例创建的最后一节,前面已经将一个实例通过不同方式(工厂方法、构造器注入、默认构造器)给创建出来了,下面我们要对创建出来的实例进行一些“加工”处理。 源码解读 回顾下之前...

MarvelCode ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部