我为何需要使用空接口?
FxCop设计规则中的第三条提供了对空接口的检查.下面是它的描述:
一个接口提供了一组行为和使用契约(usage contract),任何一个类型都可以实现这个Interface, 而不需要考虑这个类型的继承层次。一个类型通过实现接口的成员而实现这个接口。一个空的接口没有定义任何成员,因此,也就没有任何契约能够被实现。
如果你的设计包含一个空的接口,并且希望一些类型实现这个接口,你很可能希望使用这个接口作为一个标记来标示一组类型。如果你只需要区分这些类型在运行时,一个更佳的解决方式是使用自定义属性(attribute)。使用有或没有一个属性或通过属性的字段(Property)去标示一组类型。如果你希望这种标示能够被使用在编译时,就只好使用空接口了。 这说明在大多数情况下,空接口都说明在设计上存在错误。这里有一个例子:
interface ThingBase {};
interface Thing1 : ThingBase
{
// Operations here...
};
interface Thing2 : ThingBase {
// Operations here...
};
考察这个定义,我们可以观察到两个事实: • Thing1 和Thing2 有共同的基类,因此是相关的。
• 不管Thing1和Thing2有什么共同之处,都可以在ThingBase接口中找到。 当然,只要看一看ThingBase,我们就会发现Thing1 和Thing2 根本没有共享任何操作,因为ThingBase 是空的。 假如我们是在使用面向对象模型,这种做法就显然很奇怪:在面向对象模型中,与某个对象通信的唯一途径是向它发送消息。但要发送消息,我们需要有操作。假如ThingBase没有操作,我们就无法向它发送消息,而Thing1 和Thing2 也就是不相关的,因为它们没有共同的操作。但看到Thing1 和Thing2 有共同的基类,我们就会得出这样的结论:它们是相关的,否则共同的基类就根本不会存在。到了这里,大多数程序员都会开始挠头,想知道到底在发生什么事情。 使用这样的设计的一个常见理由是,要多态地处理Thing1 和Thing2。 例如,我们可以继续先前的定义:
interface ThingUser
{
void putThing(ThingBase thing);
};
现在使用共同的基类的目的就清楚了:我们既想要把Thing1 代理、也想要把Thing2 代理传给putThing。这能否证明使用空的基接口是正当的? 要回答这个问题,我们需要思考一下在putThing 的实现中发生的事情。显然, putThing 不可能调用ThingBase 上的操作,因为在那里没有操作。这意味, putThing 必须要能做以下两件事情之一:
1. putThing 能够记住事物的值。 2. putThing 能够试着向下转换到Thing1 或Thing2,然后调用操作。 putThing 的伪码实现看起来可能像是这样:
void putThing(ThingBase thing)
{
if (is_a(Thing1, thing))
{
// Do something with Thing1...
} else if (is_a(Thing2, thing)) {
// Do something with Thing2...
} else {
// Might be a ThingBase?
// ...
}
}
这个实现试着依次把它的参数向下转换成每种可能的值,直到它找 到参数实际的运行时类型。当然,任何一本像样的面向对象课本都会告 诉你,这是在滥用继承,并且会带来维护问题。如果你发现自己在编写像putThing 这样的操作,依赖于人为的基接口,问问你自己,你是否真的需要采用这种做法。例如,这样的设计可能更加适宜:
interface Thing1 {
// Operations here...
};
interface Thing2 {
// Operations here...
};
interface ThingUser {
void putThing1(Thing1 thing);
void putThing2(Thing2 thing);
};
在这种设计中, Thing1 和Thing2 是不相关的,而ThingUser 为每种类 型的代理提供了单独的操作。这些操作的实现不需要使用任何向下转换,而且在我们的面向对象世界里,一切都安然无恙。 下面是空的基接口的另一种常见用法:
interface PersistentObject {};
interface Thing1 : PersistentObject
{
// Operations here...
};
interface Thing2 : PersistentObject
{
// Operations here...
};
显然,这种设计把持久功能放在PersistentObject 基接口中,并且要求想要拥有持久状态的对象继承PersistentObject。表面上,这是合理的:毕竟,这样使用继承是一种沿用已久的设计模式,那么,它可能有什么问题?我们发现,这种设计有这样一些问题: • 上面的继承层次用来给 Thing1 和Thing2 增加行为。但在严格的OO 模型中,行为只能通过发送消息来调用。这引发了这样一个问题:PersistentObject 实际上该怎样着手完成它的工作;推测起来,它对Thing1 and Thing2 的实现(也就是,内部状态)有所了解,所以它可以把该状态写入数据库。但如果是这样, PersistentObject、Thing1,以及Thing2 就不能再在不同的地址空间中实现了,因为如果是那样, PersistentObject 就不再能知道Thing1 和Thing2 的状态。 换一种做法, Thing1 和Thing2 可以使用PersistentObject 提供的某种功能, 使它们的内部状态持久。但PersistentObject 没有任何操作,那么Thing1 和Thing2 实际上又该怎样去完成这件事情呢?再一次,唯一可行的做法是,在同一个地址空间中实现PersistentObject、Thing1,以及Thing2,并让它们在幕后共享实现状态,也就是说,它们不能在不同的地址空间中实现。 • 上面的继承层次把世界分成两半,一个含有持久对象,另一个含有非持久对象。这种做法有着深远的影响:
• 假定你有一个应用,它已经实现了一些非持久对象。随着时间推移,需求发生变化,你发现现在你想让部分对象持久。采用上面的设计,你无法做到这一点,除非你改变你的对象的类型,因为它们现在必须继承PersistentObject。这当然是一个极其糟糕的消息:你不仅要改变你的服务器中的对象的实现,还要找到并更新所有正在使用你的对象的客户,因为它们突然有了一种全新的类型。更糟糕的是,你无法让它们向后保持兼容:或者让所有客户随着服务器发生改变,或者一个客户都不改变。要想让某些客户“不升级”,这是不可能的。
• 这种设计不能扩展到支持多种特性。设想一下,我们有另外一些行为,对象可以继承它们,比如序列化、容错、持久,以及用搜索引擎进行搜索的能力。我们很快就会陷入多重继承的泥淖。更糟糕的是,每种可能的特性组合都会创建一种完全独立的类型层次。这意味着,你不再能编写出一些操作,一般化地对一些对象类型进行操作。例如,你不能把持久对象传到需要非持久对象的地方, 即使对象的接收者并不在乎对象的持久方面。这很快就会造成碎片化的、难以维护的类型系统。不久,你会发现,你不是在重写应用,就是获得了某种难以使用也难以维护的东西。
但愿前面的讨论成为一个警告:空接口几乎总是表明,你的应用通过所定义的接口之外的机制共享了实现状态。如果你发现自己在编写空的接口定义,你至少应该后退一步,思考一下手上的问题;其他设计可能会更加适宜,更能清晰地表达你的意图。如果无论如何你都要使用空接口,那么要注意,你几乎肯定会失去这样的能力:改变对象模型在物理的服务器进程上的分布方式,因为你无法把共享了隐藏状态的接口分置在不同的地址空间中。
- 神经网络开始放飞自我!都是因为架构搜索新算法
- 浏览器缓存问题的解决
- nginx下目录浏览及其验证功能、版本隐藏等配置记录
- Sqlite的多表连接更新
- Enterprise Library 4.1学习笔记6----加密应用程序块
- 浅谈数据库主键策略
- nginx应用总结(1)--基础认识和应用配置
- nginx反向代理tomcat访问时浏览器加载失败,出现 ERR_CONTENT_LENGTH_MISMATCH 问题
- Enterprise Library 4.1学习笔记5----实体验证程序块
- Python防止sql注入
- 电工学PLC编程的入门建议
- 人工智能、区块链、图灵测试……这30个大数据热词你真的都懂吗?
- Enterprise Library 4.1学习笔记4----缓存应用程序块
- 设置py文件的路径
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 在Keras中利用np.random.shuffle()打乱数据集实例
- 浅谈matplotlib中FigureCanvasXAgg的用法
- Keras自定义实现带masking的meanpooling层方式
- 利用keras使用神经网络预测销量操作
- 获取python运行输出的数据并解析存为dataFrame实例
- 如何使用Cython对python代码进行加密
- PHP快速排序算法实现的原理及代码详解
- 从ThinkPHP3.2.3过渡到ThinkPHP5.0学习笔记图文详解
- keras实现VGG16 CIFAR10数据集方式
- PyTorch: Softmax多分类实战操作
- 为什么称python为胶水语言
- opencv 图像礼帽和图像黑帽的实现
- python文件及目录操作代码汇总
- 使用pyplot.matshow()函数添加绘图标题
- 如何卸载python插件