C++ 代码整洁之道:C++17 可持续软件开发模式实践 (30):原则 3.5

阅读数:7 2019 年 12 月 4 日 18:49

C++代码整洁之道:C++17可持续软件开发模式实践(30):原则 3.5

(信息隐藏原则)

内容简介
本书致力于讲述 C++ 整洁代码之道!如果你想让自己写的代码更加整洁,那么这本书适合你阅读。本书需要熟悉 C++ 语言的基本概念,才能有效的掌握其中的内容。如果你只是想从 C++ 开发开始,并且没有 C++ 语言的基础知识,你应该首先选择一个好的 C++ 入门的练习项目。此外,本书也不包含任何深奥的技巧和杂乱的知识点。我知道 C++ 有很多令人兴奋的技巧,但这些通常不是整洁代码的精神,也不是现代 C++ 的代码风格。除此之外,这本书为了帮助 C++ 程序员提高技能水平,并举例说明如何编写易于理解的、灵活的、可维护的和高效的 C++ 代码。即使你是一个经验丰富的 C++ 开发人员,这本书中也有一些值得学习的地方,我认为这些值得学习的地方能够促进你的工作。书中所提出的原则和实践可以应用于新的软件系统,有时被称为“绿地项目”,以及具有悠久历史的遗留系统,通常被称为“棕地项目”。

信息隐藏原则是软件开发中一个众所周知的基本原则,它首先记录在开创性论文“On the Criteria to Be Used in Decomposing Systems Into Modules”[Parnas72] 中,由 David L. Parnas 于 1972 年撰写。

该原则指出,一段代码调用了另外一段代码,那么,调用者不应该知道被调用者的内部实现。否则,调用者就有可能通过修改被调用者的内部实现而完成某个功能1,而不是强制性地要求调用者修改自己的代码。

1 因为调用者知道了被调用者是如何实现的,所以可以修改。—译者注

David L. Parnas 认为信息隐藏是把系统分解为模块的基本原则,Parnas 同样认为系统模块化是为了隐藏困难的设计决策或可能改变的设计决策,应该涉及隐藏困难的设计决策或可能改变的设计决策,软件单元(例如,类或组件)暴露于其环境的内部构件越少,该单元的实现与其客户端之间的耦合就越低。因此,软件单元内部实现的更改将不会被其使用者所察觉。

信息隐藏有很多优点:

  • 限制了模块变更的范围。
  • 如果需要修复缺陷,对其他模块的影响最小。
  • 显著提高了模块的可复用性。
  • 模块具有更好的可测试性。

信息隐藏通常与封装混淆,但其实它们不一样,这两个术语在许多著名的书籍中是同义词,但我并不这么认为。信息隐藏是帮助开发人员找到好的设计模块的原则,该原则适用于多个抽象层次并能展现其正面效果,特别是在大型系统中。

封装通常是依赖于编程语言的技术,用于限制对模块内部的访问。例如,在 C++ 中,你可以在 private 关键字后定义一些类成员,以确保类外部无法访问它们,但我们仅用这种防护方式进行访问控制,离自动隐藏信息还远着呢,封装有助于但不能保证信息隐藏。

以下代码示例展示了隐藏信息较差的封装类:

代码 3-1 一个自动转向门的类(摘录)

复制代码
class AutomaticDoor {
public:
enum class State {
closed = 1,
opening,
open,
closing
};
private:
State state;
// ...more attributes here...
public:
State getState() const;
// ...more member functions here...
};

这不是信息隐藏,因为类内部的实现部分暴露给了外部环境,尽管该类看起来封装得很好。注意 getState 返回值的类型,客户端用到的枚举类 State 用到了这个类,如下示例所示:

代码 3-2 必须使用 AutomaticDoor 查询门的当前状态的示例

复制代码
#include "AutomaticDoor.h"
int main() {
AutomaticDoor automaticDoor;
AutomaticDoor::State doorsState = automaticDoor.getState();
if (doorsState == AutomaticDoor::State::closed) {
// do something...
}
return 0;
}

枚举类(结构体)[C++11]
在 C++11 中,枚举类型也有了创新。为了向下兼容早期的 C++ 标准,现在仍存在众所周知的枚举类型及其关键字 enum。从 C++11 开始,我们还引入了枚举类和枚举结构体。
旧的 C++ 枚举类型有一个坏处是,它们将枚举成员引入周围的命名空间,导致了名称冲突,如下示例所示:

复制代码
const std::string bear;
// ...and elsewhere in the same namespace...
enum Animal { dog, deer, cat, bird, bear }; // error: 'bear' redeclared as different
kind of symbol
{1}

此外,旧的 C++ enum 会隐式转换为 int,当我们不预期或不需要这样的转换时会导致难以察觉的错误:

复制代码
enum Animal { dog, deer, cat, bird, bear };
Animal animal = dog;
int aNumber = animal; // Implicit conversion: works

当使用枚举类(也称为“新枚举”或“强枚举”)时,这些问题将不再存在,它们的枚举成员对枚举来说是局部的,并且它们的值不会隐式转换为其他类型(比如另一个枚举或 int 类型)。

复制代码
const std::string bear;
// ...and elsewhere in the same namespace...
enum class Animal { dog, deer, cat, bird, bear }; // No conflict with the string named
'bear'
Animal animal = Animal::dog;
int aNumber = animal; // Compiler error!

对于现代 C++ 程序,强烈建议使用枚举类而非普通的旧的枚举类型,因为它使代码更安全,并且因为枚举类也是类,所以它们可以前向声明。

如果必须更改 AutomaticDoor 的内部实现并从类中删除枚举类 State,那么会发生什么呢?很容易看出它会对客户端的代码产生重大影响,它将导致使用成员函数 AutomaticDoor::getState() 的所有地方都要进行更改。

以下是具有良好的信息隐藏性的封装的 AutomaticDoor 类:

代码 3-3 一个更好的自动门转向设计类

复制代码
class AutomaticDoor {
public:
bool isClosed() const;
bool isOpening() const;
bool isOpen() const;
bool isClosing() const;
// ...more operations here...
private:
enum class State {
closed = 1,
opening,
open,
closing
};
State state;
// ...more attributes here...
};

代码 3-4 类 AutomaticDoor 被修改后的简洁例子

复制代码
#include "AutomaticDoor.h"
int main() {
AutomaticDoor automaticDoor;
if (automaticDoor.isClosed()) {
// do something...
}
return 0;
}

现在,修改 AutomaticDoor 类的内部要容易实现得多。客户端代码不再依赖于类的内部实现。现在你可以在不引起该类任何用户注意的情况下,删除 State 枚举并将其替换为另一种实现。

C++代码整洁之道:C++17可持续软件开发模式实践(30):原则 3.5

购书地址 https://item.jd.com/12599914.html?dist=jd

评论

发布