【选择恐惧症】接口?虚基类?

  • 症前兆
  • 症分析
  • 症解答
  • 症总结

症前兆

记得有个朋友跟我讨论过这样的一个问题,说到他刚刚学习接口虚基类的相关知识时觉得很迷茫,不知道什么时候该用接口,什么时候该使用虚基类。后来慢慢地发现接口能做的事情,虚基类也能够实现,甚至有更多的特点。再后来就慢慢地放弃了接口,把所有的设计和实现都采用虚基类来替代。不能说我这个朋友这样的处理有错,但是就我个人对接口和虚基类的理解来说,这样的做法是有不妥的地方。

症分析

所谓的接口简单的来说就是个“门口”,而这个"门口"是安装在某个模块或者服务上,其目的就是为了让外面的世界通过这个“门口”可以访问到模块上的功能或服务。由于是跟外部环境做对接,因此给它定义为–接口。而虚基类则更像一间毛胚房,整个架子已经有了(包括门口),想要什么东西就直接往里面放,但是摆放的东西跟整个架子的设计有关,不是所有的东西都能乱摆,就好像原本规划为洗手间的空间,总不能把床摆在里面吧(当然,你乐意也是可以的。)。

症解答

说到这里,其实已经能够感觉到它们的区别是什么了,表面上虚基类感觉更加强大一点,可以像接口那样声明一系列的方法(这里的方法是没有实现体的,在虚基类中我们把这类方法叫“虚方法”),又能定义一些共有的属性;但是,因为虚基类也是一个类型,是必须要继承与它才能够拥有这样的一些特性,所以这就是它的限制和约束。

接口总的来说是比虚基类要更加灵活一点,因为它没有涉及到类的层面,只跟类中方法绑定,不需要指定其类型。也就是说类型实现了接口中所定义的方法,那么,则可以为外部提供这样的功能。说得通俗一点就是门口你可以随便在哪间房子上开。而虚基类则不具有这样的能力。我们用代码来解释一下上面所说的。

//定义接口
interface IAction
{function run();
}//定义一个Person类
class Person : IAction
{function run(){print("person run...");}
}//定义一个Dog类
class Dog : IAction
{function run(){print("dog run...");}
}

上面代码中定义了一个IAction的接口(一般的高级编程语言中都用interface这个词来表示接口,在Objective-C中则使用了Protocol一词来表示接口,其实也挺贴切,因为要调用接口的功能就是要按照其指定的协议来实现,包括传什么样参数,返回什么值),Person和Dog分别实现了IAction接口,可以看到Person和Dog是两个毫无关系的类型。

如果换作是虚基类则无法将这两种类型关联起来,因为实现的类型必须继承该虚基类,但是,有一种变通的做法就是对要关联的类型进行更高层次的抽象,那上面的例子来说,因为Person和Dog都属于动物,因此我们可以把虚基类定义为Animal类型。则有下面的做法:

//定义虚基类Animal
virtual class Animal
{//定义虚方法runvirtual function run() : void;
}//继承于Animal的Person类
class Person : Animal
{function run(){print("person run...");}
}//继承于Animal的Dog类
class Dog : Animal
{function run(){print("dog run...");}
}

通过这样的做法确实是能够达到想要的效果, 但是如果你之前已经设计好了一个虚基类,对于后续需要在设计中加入这种不相关的类型,那么你就需要调整之前设计好的虚基类了,明显要花费额外的时间去做一些重构。

所以,设计时要选择使用接口还是虚基类?我个人觉得虚基类不适合作为提供外部调用。因为他与类型结构绑定,日后如果要进行调整就会影响对外行为。但是它可以作为内部某些业务处理的公共封装,配合类工厂模式屏蔽类型上的差异。例如写一个数据存储服务,它可能是文件存储,也可能是数据库存储,我们可以进行如下定义:

//定义数据存储服务的虚基类
virtual class DataStoreService
{//定义保存数据的纯虚方法virtual function saveData(data : Object) : void;
}//定义文件数据存储服务类型
class FileStoreService : DataStoreService
{var _file:File;function saveData(data : Object) : void{_file.writeData(data);_file.save();}
}//定义数据库存储服务类型
class DatabaseStoreService : DataStoreService
{var _db:Database;function saveData(data : Object) : void{_db.insertData(data);_db.flush();}
}//定义一个数据存储类工厂
class DataStoreFactory
{//定义数据存储方式enum DataStoreType{File,Database}//获取数据存储服务方法function getDataStoreService(type : DataStoreType) : DataStoreService{switch (type){case File:return new FileStoreService();case Database:return new DatabaseStoreService();}}
}

如上述代码所示(上面写的都是伪代码,只用于说明意图),只要使用DataStoreFactory然后根据自己需要的存储类型就能获取到不同的存储服务,而返回的类型是定义的虚基类DataStoreService,这样就能够很好地屏蔽FileStoreService和DatabaseStoreService中的一些设计细节,因为对于调用的人来说这些都可以是透明的。

接口正是我们需要对外提供功能的一个比较好的方案。一来它不跟类型挂钩,二来又能像虚基类中的纯虚函一样可以屏蔽内部实现,对调用者透明不需要他理解里面的实现原理,只管调用和取得结果。第三个就是对于日后内部设计的升级改造时,无需改变接口的定义,只要把内部实现进行调整即可。我们来举个例子,假如之前我们一直使用文件作为主要的存储方式,那么使用接口来实现,可以类似如下代码:

//定义数据存储服务接口
interface IDataStoreService
{function saveData(data : Object) : void;
}//定义文件存储服务,该类型不对外公开
class FileStoreService : IDataStoreService
{var _file : File;function saveData(data : Object) : void{_file.writeData(data);_file.save();}
}//对外公开的Api类型
class Api
{function getDataStoreSerivce( ) : IDataStoreService{return new FileStoreService( );}
}

值得注意的是,我们在设计时必须是要有一个对外公开的类,否则无法让外部可以访问到内部所提供的接口,上面代码提供公开类就是Api类型。从代码上来看我们的Api类型的getDataStoreService方法只返回了一个IDataStoreService的接口,并不涉及到FileStoreService。所以,当我们在进行改造时,可以直接把文件存储改为数据库存储,也不会对外部调用造成任何影响,如下面代码变更:

//定义数据存储服务接口
interface IDataStoreService
{function saveData(data : Object) : void;
}//定义数据库存储服务类型
class DatabaseStoreService : IDataStoreService
{var _db:Database;function saveData(data : Object) : void{_db.insertData(data);_db.flush();}
}//对外公开的Api类型
class Api
{function getDataStoreSerivce( ) : IDataStoreService{return new DatabaseStoreService( );}
}

回到最初我朋友的那个问题,其实要使用虚基类还是接口来实现功能,这两者其实是没有任何冲突的,最好是两者结合使用,虚基类作为内部封装的公共元素而存在,可以根据领域的不同划分多个不同的虚基类,而在虚基类中定义的某项功能需要暴露给外界调用时,则可以使用接口来定义,同样根据不同的领域可以划分多个不同的接口。还是根据上面的例子,我们把虚基类接口相结合,形成一个完整的数据存储服务模块:

//定义数据存储服务接口
interface IDataStoreService
{function saveData(data : Object) : void;
}//定义数据存储服务的虚基类
virtual class DataStoreService : IDataStoreService
{//实现接口方法function saveData(data : Object) : void{//由于实现接口的类型不允许不实现接口方法,//因此这里保留一个空实现方法,等待它的子类重写该方法。}
}//定义文件数据存储服务类型
class FileStoreService : DataStoreService
{var _file:File;function saveData(data : Object) : void{_file.writeData(data);_file.save();}
}//定义数据库存储服务类型
class DatabaseStoreService : DataStoreService
{var _db:Database;function saveData(data : Object) : void{_db.insertData(data);_db.flush();}
}//定义一个数据存储类工厂
class DataStoreFactory
{//定义数据存储方式enum DataStoreType{File,Database}//获取数据存储服务方法function getDataStoreService(type : DataStoreType) : DataStoreService{switch (type){case File:return new FileStoreService();case Database:return new DatabaseStoreService();}}
}//对外公开的Api类型
class Api
{function getDataStoreSerivce( ) : IDataStoreService{return DataStoreFactory.getDataStoreService(DataStoreType.Database);}
}

症总结

接口 用于提供给外部调用的入口,根据功能领域的不同来划分不同的接口。其不与类型绑定,只跟类型中的成员方法相关。方便日后内部的升级改造,不影响对外提供的服务。

虚基类 用于内部封装类型的共有特征,由于虚基类不能直接实例化,因此可以起到屏蔽子类实现细节的效果。搭配类工厂来实现不同业务分派给不同的子类来进行处理。

在很多高级语言中两者都有定义(即使没有也可以代码层面去模仿和约定),善用这两种定义能够使自己的设计变得简单,结构变得清晰。

#其他症状

《【选择恐惧症】需不需要通用设计?》

【选择恐惧症】接口?虚基类?相关推荐

  1. 【选择恐惧症】需不需要通用设计?

    [选择恐惧症]需不需要通用设计? 症前兆 症分析 症解决 症前兆 经常会遇到这样的问题:一份需求下来,在做设计的时候就会开始纠结了:到底需不需要先抽象公共元素进行通用设计,再来进行功能的细化设计呢?还 ...

  2. C++ 多继承类 虚基类

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/jzj_c_love/article/d ...

  3. 选择恐惧症与产品设计

    选择恐惧症与产品设计 产品经理朝阳陆  微信:yak1982  微博:产品经理朝阳陆 在本文正式开始之前,先给大家进行一个小测试,请看下图并回答:如果是男人,你会喜欢哪一种类型的女友?如果是女人,你结 ...

  4. 38.【C++ 虚函数 纯虚函数 虚基类 (最全详解)】

    虚函数.虚基类.纯虚函数 (一).虚函数 1.什么是虚函数: 2.虚函数的格式: 3.关于虚函数的注意事项: 4.虚函数的作用: 5.虚函数访问格式 6.虚函数的各种疑难杂症 [当指针是基类.但虚函数 ...

  5. php 虚基类,1.9 多态

    1.9 多态 返回目录 1 面向对象技术 上一节 1.8 继承 多态(Polymorphism)按字面的意思就是"多种状态".在面向对象语言中,接口的多种不同的实现方式即为多态. ...

  6. 虚函数,虚基类 与纯虚函数 二

    虚函数    还是先看代码 class A { public:     void funPrint(){cout<<"funPrint of class A"<& ...

  7. 虚基类及其派生类的构造函数

    虚基类的概念及用法 如果派生类的全部或者部分基类有共同的基类,那么派生类的这些直接基类从上一级基类继承的成员都具有相同的名称,定义了派生类的对象后,同名数据成员就会在内存中有多份拷贝,同名函数也会有多 ...

  8. C++ - 虚基类、虚函数与纯虚函数

    虚基类        在说明其作用前先看一段代码 class A { public:    int iValue; }; class B:public A { public:    void bPri ...

  9. 【虚基类、虚函数及应用】

    虚基类 1.虚基类存在的意义 当在多条继承路径上有一个公共的基类,在这些路径中的某几条汇合处,这个公共的基类就会产生多个实例(或多个副本),若只想保存这个基类的一个实例,可以将这个公共基类说明为虚基类 ...

最新文章

  1. “以史为鉴”-企业信息化的梳理-前言
  2. java 多线程 共享数据_JAVA多线程提高四:多个线程之间共享数据的方式
  3. android 判断手机为小米
  4. β射线与哪些物质可产生较高的韧致辐射_辐射无所不在,香蕉土豆里都有?我们还能愉快生活吗?...
  5. 【BZOJ-3033】太鼓达人 欧拉图 + 暴搜
  6. MySQL深度剖析之SQL语句更新流程(2021)
  7. web.xml中的主要元素说明(listener, filter, servlet)
  8. java 线程间的通讯(升级版)
  9. java 抽象属性_怎样在java中定义一个抽象属性
  10. Quartus 与 ModelSim 联合仿真详细步骤
  11. 【若依框架】代码生成详细教程
  12. Lightning 转 USB Type-A/Type-C 思路
  13. python爬虫:爬取携程航班数据
  14. abap中方法file_open_dialog的使用
  15. [JAVA面试] java面试
  16. B站这场跨年晚会价值60亿,凭啥?
  17. vue canvas画框调大小
  18. uniapp 微信小程序发布
  19. GitHub——Gist
  20. 力扣-股票的资本损益

热门文章

  1. Android运营商名称的获取及显示过程
  2. 纯JS实现填字游戏实战总结
  3. SonarQube-代码质量检测工具
  4. libcrypto-1_1.dll丢失,要怎么处理?
  5. java 图片url转图片
  6. ECShop 后台订单列表美化
  7. STM32.UART5无法进入中断,HardFault
  8. Centos7的安装与模板机的制作
  9. 零伽壹链改研究:区块链技术在支付及清结算的变革与创新!
  10. Ajax 不显示BlockUI 的问题