《C++API设计》读书笔记(2):好的API的特征

本章内容

本章内容就是在回答一个问题:好的API应该具备哪些特征?

下面就是从几个方面来解释应具备的特征:

1. 对于问题有良好的建模

API的目的是为了解决某一方面的问题。因此,首先应对问题有个清晰的认识,能塑造出这个问题里的核心对象。

1.1 抽象

设计API时应阐述有意义的深层概念,而非公开底层细节。
(可以让非技术人员看接口,如果连他们都能看懂这个接口会干什么,那就说明接口的抽象非常到位了)

1.2 对象建模

确定问题牵扯的主要对象有哪些,他们各自提供什么操作,他们之间的关系。

然而,不要太极端,不要创建比需求更加通用的对象模型。

2. 隐藏细节

任何内部实现细节(那些很可能变更的部分)都必须对API的客户隐藏。
手段上分为物理隐藏逻辑隐藏

对于C++来说,物理隐藏指将公开的接口(.h)和内部细节(.cpp)分开放于不同的文件。

逻辑隐藏封装,即通过语言特性来限制访问内部
对于C++来说有几个方面要注意:

2.1 隐藏成员变量

不要把成员变量设置为public。
如果要访问成员,应该使用getter和setter方法来间接地访问。这有以下几个好处:

  • 可在访问时验证有效性,比如拒绝输入的错误值。
  • 惰性求值。可仅在需要时再执行计算。
  • 缓存
  • 额外的计算,比如提供一些附加操作。
  • 可发布通知
  • 便于调试
  • 线程同步
  • 更精确的访问控制,比如只读。
  • 维持和其他数值的关系

也最好不要把成员变量设置为protected。正如AlanSnyder所说,继承损害了封装所带来的好处。

扫描二维码关注公众号,回复: 15110517 查看本文章

2.2 隐藏实现方法

类只应定义做什么而非如何做。

(很遗憾,C++由于语言限制,private成员也要出现在类的声明中。但是之后会介绍Pimpl技巧来解决这个问题)

2.3 隐藏实现类

并非所有类都应该公开。仅用于实现的类应该从API的公开接口中移除。

3. 最小完备性

优秀的API应该是最小完备的。

Occam剃刀原理:若无必要,勿增实体。

3.1 不要过度承诺

不确定某个接口是否需要时,就不要提供。

工程师希望解决方案更通用和灵活,但是要抵制这种诱惑,因为:

  • 想要添加的通用性可能永远用不到。
  • 当未来真的需要时再添加,那时你会拥有更多的知识。
  • 当到了确实需要添加功能时,简单的API比复杂的API更容易添加。

3.2 谨慎添加虚函数

虽然继承十分强大,但是有潜在隐患:

  • 基类变得脆弱,不好修改。
  • 客户继承后的行为无法控制,可能会做出本来不允许的行为。
  • override函数可能会破坏内部完整性。

因此要谨慎使用继承。仅当子类和基类形成一种 is-a 关系时,继承才是有意义的。

3.3 分离“核心API”和“便捷化API”

“减少API函数数目” 和 “使API易于各种客户使用” 有天然的矛盾。
API傻瓜时会影响灵活度,但是复杂时会影响使用难度。

因此,将“核心API”和“便捷化API”放在不同的地方。这样专家可以用其实现高级功能,但又不影响普通用户的使用。

4. 易用性

API应易于理解。

4.1 自说明性

指用户能够通过API自身明白如何使用它。

比如一个清晰的、描述性强的名字。

4.2 不易误用

反例如函数参数中前后两个bool,很容易混淆。这时可以使用枚举类作为参数。

4.3 一致性

采用一致的风格,更易于用户理解。简化了用户学习过程。
比如一致的命名风格、相同的词语应表达相同的概念。

4.4 正交性

东西坐标的变化不应影响南北坐标。
比如对水压设置的接口不应影响温度。

需要铭记两个重点:

  • 减少冗余。确保每个信息都只有唯一权威的来源。
  • 增加独立性。确保暴露的概念没有重叠。

4.5 健壮的资源分配

C++指针无法分辨自己是否引用合法内存,因此这交给了程序员自己判断。有些解决方案,比如共享指针。

4.6 平台无关

不要将平台相关的内容放在公开的接口中,这会使你的接口平台相关。

5. 松耦合

  • 耦合:组件之间相互连接的强度,即系统中每个组件对其他组件的依赖程度。
  • 内聚:组件内部各种方法相互关联的程度。

优秀API表现为:松耦合,高内聚

一种理解耦合的方式是:当A改变时需要改变B中多少代码。

5.1 仅依赖名字

在C++中,如果你只需要知道另一个类的名字,不需要知道它的大小或调用它的任何方法,那就可以使用前向声明

5.2 降低类的耦合

如果可以,则优先使用非成员函数(static函数)。因为成员函数会访问所有成员,耦合度更高。

5.3 有时需要重复来降低依赖

如果依赖另一个组件只是因为需要一个微不足道的小功能,那么此时直接复制过来代码是可以接受的。

5.4 Manager类

可用一个Manager类来连接各个类,这样他们各自的耦合度就会降低。

5.5 回调、观察者、通知

假设A要告诉B事情,则A就要依赖B。但是如果之后A又要告诉C,则A还要依赖C。那么更好的方式是注册成监听器,这样A就不用直接依赖B和C了。

6. 稳定性、文档、自动测试

此部分在后面章节有更详细的介绍。

猜你喜欢

转载自blog.csdn.net/u013412391/article/details/128427730
今日推荐