博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
《C++代码设计与重用》——2.6 接口一致性
阅读量:6978 次
发布时间:2019-06-27

本文共 3199 字,大约阅读时间需要 10 分钟。

本节书摘来自异步社区出版社《Imperfect C++中文版》一书中的第2章,第2.6节,作者: 【美】Martin D.Carroll , Margaret A.Ellis,更多章节内容可以访问云栖社区“异步社区”公众号查看。

2.6 接口一致性

C++代码设计与重用

2.6 接口一致性
在类的内部和类与类之间,在程序库的内部和程序库与程序库之间,我们都应该尽可能地保持类接口的一致性。很显然,保持程序库与程序库之间的接口一致性,将比保持单个程序库内部的接口一致性更加困难。

接口一致性的重要性要归因于以下几个方面。首先,具有接口一致性的类易于学习和记忆。例如,假设我们正在设计一个容器类程序库。(容器类是一种用来保存值或者对象的集合的类。)下面就是我们设计的程序库所提供的两个类:

template
class Set { public: void insert(const T& t); //... }; template
class Bag { public: void insert(const T& t); //... };

一个Set是类型为T的值的集合;一个Bag是类型为T的值的汇合。(Bag和Set是有区别的,Bag允许它里面的相同元素值出现多次,而Set就不允许。)尽管在Set里插入一个值和在Bag里插入一个值的区别很小(在Bag里插入一个已经存在的值,将会出现该值的一份拷贝,而Set就不会),我们还是给予这两个类的插入操作相同的句法接口(syntactic interface)。因为如果插入操作拥有不同的句法接口的话,那么对Set类和Bag类的学习、使用、记忆将会显得更加困难。

提供接口一致性的一个稍微次要的原因就是,它使用户可以更加容易地改变程序中对象的类型,假设用户已经完成了下面代码:

Set
s; //... s.insert(7);

如果写完了上面的代码之后,用户发现应该使用Bag而不是Set,那么要是Set和Bag拥有一致性接口的话,这时我们就可以只更改上面的声明语句,而不需要更改调用语句:

Bag
s; //在这里将Set改成Bag... //... s.insert(7); //...这一行不需要更改

具有相似接口的类可以从一个公共基类派生而来,譬如下面的代码:

template
class Container { public: virtual void insert(const T& t) = 0; //... }; template
class Set : public Container
{ public: virtual void insert(const T& t); //... };
//类Bag和类Set类似,继承自类Container...

然而,两个类仅仅具有相似的接口,并不意味着它们就应该派生自某个公共基类。假设我们的用户并不需要在Set和Bag上实现具有多态性的函数,并且如果我们希望避免由于实现为虚函数而给insert函数带来的额外开销,那么我们就可以决定不让类Set和类Bag派生自基类Container。

决定两个接口的一致性程度应该经过深思熟虑。假设我们也提供一个类Queue来描述队列,那么类Queue是否应该提供insert操作呢?

template
class Queue { public: void insert(const T& t); //应该提供这个函数吗? //... };

如果我们提供类Queue的insert操作,那么我们将在Queue的什么位置插入t呢?很显然,有两个合理的位置:队头和队尾。然而,这两个选择都是不可取的。假设选择了队头,我们还是必须提供一个在队尾进行插入的操作。另外,因为我们不能把这两个操作都命名为insert,所以我们就必须给其中一个操作命名为别的名字:

template
class Queue { public: void insert(const T& t); void insert_tail(const T& t); //... };

然而,上面的接口就会出现内部的不一致性。许多用户往往记不清楚类Queue究竟是提供insert/insert_tail两个操作,还是提供insert/insert_head两个操作。这样,一些用户就会调用insert函数,并想当然地认为insert执行的是队尾插入操作。

内部一致性的接口应该给这两个操作分别命名为insert_head和insert_tail:

template
class Queue { public: void insert_head(const T& t); void insert_tail(const T& t); //... };

然而,上面这个Queue类的接口将会与Set类和Bag类的接口不一致;为了恢复它们之间的一致性,我们可以提供下面3个操作,并把insert定义为insert_head的等价操作:

template
class Queue { publiC: void insert(const T& t) {insert_head(t); } void insert_head(const T& t); void insert_tail(const T& t); //... };

很遗憾的是,这些接口比前面两种接口都要大,从而难以理解。

要让类Queue的接口与类Set和类Bag的接口保持一致性是很困难的,这主要是由于类Queue具有不同于类Set和类Bag的地方:类Queue逻辑上要实现两个插入操作。让我们考虑另一个像Set和Bag的类吧,它只提供一个插入操作。特别地,考虑描述堆栈的类Stack。堆栈中只有一种插入方式,就是把插入值压入栈顶。我们是应该把这个插入操作命名为insert来和Set和Bag保持一致性呢?还是应该把它命名为push呢?因为push是人们对堆栈插入操作的习惯称呼;或者命名为两个呢?我们的看法是,提供一个push操作将使大多数类Stack的用户能够更容易地学习和记忆—特别地,对那些只使用类Stack,而对我们程序库中其他的容器类并不熟悉的用户而言,就显得更加重要了。另外,并不是所有的用户都会把他们程序中的Stack替换成Set或Bag,或者把Set或Bag替换成Stack。因此,这种只提供push操作但导致不一致性的接口,在这里可能是更可取的。

接口应该尽可能地保持一致性,但也不能用一致性来代替其他的一切因素。如果一致性导致了类的接口产生对这个类不适当或者负面的影响,那么我们就不应该使用一致性。

本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。

你可能感兴趣的文章
cglib代理的使用
查看>>
Format specifies type 'id' but the argument has type 'NSError *__autoreleasing *
查看>>
[译] JWT 与 Spring Cloud 微服务
查看>>
Android NDK开发之旅31 FFmpeg音频解码
查看>>
关于Android开源库分享平台,(GitClub)微信小程序的开发体验
查看>>
Thrift RPC 系列教程(4)——源码目录结构组织
查看>>
CentOS 部署 flask项目
查看>>
Kotlin学习笔记-基础语法
查看>>
Mybatis中Oracle和Mysql的Count字段问题
查看>>
[Java实现] 图片择优(选择最清楚的图片)
查看>>
【译】使用Kotlin和RxJava测试MVP架构的完整示例 - 第1部分
查看>>
前端项目如何管理
查看>>
优秀Java程序员应该知道的20个实用开源库
查看>>
极限编程 (Extreme Programming) - 迭代计划 (Iterative Planning)
查看>>
这些资源网站为什么能获得5万知乎大佬推荐,而我错失了什么吗?
查看>>
PHP面试常考内容之Memcache和Redis(2)
查看>>
GDB 调试 Mysql 实战(二)GDB 调试打印
查看>>
Work with Alexa :Echo匹配连接到Alexa
查看>>
vue-cli3环境变量与分环境打包
查看>>
PHPUnit实践三(构建模块化的测试单元)
查看>>