《那些优秀API的特征》总结笔记

2020年01月27日 581点热度 0人点赞 0条评论


最近读《C++ API Design》,其中第二章《优质API的特征》可以在网上找到
原文链接。 觉得讲得非常实用,对于做程序库/API开发的程序员来说是个很好的总结,把相关要点做成了思维导图:

good-api-features

以下是对一些要点的总结归纳:

问题域建模

API 应该首先为问题提供一个清晰的解决方案,同时能对实际的问题域进行准确的建模。

  1. 提供良好的抽象, API对它所解决的问题需要提供良好的逻辑抽象,** 对问题进行抽象,而不是公开底层实现细节**
  2. 在面向对象中,需要对问题域中的关键对象建模:面向对象设计,对对象的层次结构设计。

隐藏实现细节

个人认为这个是API设计和编码规范中非常重要的一点, 出发点是保持接口稳定:要保持接口稳定,需要把接口和实现分离,暴露接口,隐藏实现,这样在实现更改的时候不需要改接口,从而避免因为API实现变动需要更改客户代码。除此之外,出于商业保密的需要,某些API也需要隐藏实现。

物理隐藏: .h 和 .cpp分离

在 C++中,声明和定义具有不同的含义,可以将声明放在 .h文件中, 定义和实现放在 .cpp文件中; .h中的声明是暴露给用户的,.cpp中实现了具体的定义,发现时 .h 文件会交付给用户,而 .cpp 会被编译成动态或者静态链接库如windows的 .dll, linux的.so
这种把接口和实现放在两个不同的文件的方式称为 物理隐藏

逻辑隐藏:封装

利用 C++ 的访问控制特性: public、 private 和protect 特性限制用户可以访问的接口,避免用户使用非公有接口。

public: 可以从类外部访问,最宽松 (struct 默认)
private: 只可以从类内部访问,最严格 (class 默认)
protect: 类内部或者派生类可以访问

隐藏成员变量

好的编程实践是:把成员变量设为私有的,必要时提供 setter和 getter 方法间接访问。好处很多,按照《C++ API Design》上说,

  • 可以在 setter方法中进行有效性验证,
  • 可以惰性求值(仅仅在getter时求值),
  • 可以提供只读方式(仅提供 setter),
  • 提供缓存(频繁访问,开销很大但是不易变的值可以先缓存下来)
  • 通知(setter时可以将值变化通知到其他模块或组件)
  • 维护不变式关系(相关联的值可以在 setter 时同时变化)
  • 提供线程安全(可以在 setter和getter方法中增加线程安全的锁,而不是要求用户提供相应的实现)

如果成员变量实例上不是逻辑接口的一部分,而是与公有接口无关的内部细节,应该隐藏。

隐藏实现方法

虽然将成员变量和非逻辑接口声明为private可以避免用户直接调用,但是用户还是有可能通过 .h 头文件了解到类的实现细节。这个时候,可以使用 "pimpl惯用法"

@comment 使用 pimpl 惯用法还可以起到隐藏依赖的作用,避免#include 进来的第三方库“传染”到客户代码中

隐藏实现类

有些类不是公开的类,仅仅是实现的一部分而不需要用户知道,这部分类不应该暴露在API的公有接口上。

最小完备性

优秀API的设计应该是最小完备的。提供用户需要的所有功能,但是不要过度设计,够用即好。

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

不要过分承诺

API中每个公有元素都是一项承诺,承诺其在生命周期中得到支持。发布的API得到使用后,增加新功能很容易,而移除或者修改接口很难。 建议是:当不确定是否需要某个接口,就不要提供此接口

谨慎地添加虚函数

允许用户重写虚函数有很多隐患,没有虚函数的类比有虚函数的类更加健壮和容易维护,必要时可以使用模板方法(Template Method Pattern)设计模式, 非虚拟接口惯用法(Non-virtual Interface idiom)。 如果必要提供含有虚函数的基类,必须谨慎。

便捷API

可以将API分为核心API和便捷Utils API:

  • 核心API提供具有原语(primitiveness)性的API,这类API的特点是最小化的、集中的, 需要访问类的内部细节以便高效实现。
  • Utils API 完全基于 Core API 构建,向用户提供了一个简单易用的封装。Utils API 以独立的模块和库的形式构建。

这种发布方式称为渐进公开。OpenGL 中就按照这个理念组织的,OpenGL核心API提供的是操作图元的底层方法,多数OpenGL的实现也包含了一个OpenGL实用库(OpenGL Utility Library, GLU),提供了更高层的函数。

易用性

好的API设计应该是易用的,风格具有一致性,不易误用(或者说采取手段防止用户误用),同时与现有的模型和概念兼容,让用户容易上手。对于C++来说,还应该具有健壮的资源分配,避免发生资源泄露。
STL中容器类的代码就具有很好的一致性,例如 vector,list,set的大小都是通过size()函数获得,这种使用相同的习惯表示相似的概念的方法,称为静态多态。

松耦合

好的API应该是松耦合,高内聚的。可以使用以下方法降低组件之间的依赖。

  • 仅提供名字耦合
  • 使用非成员、非友元的方法降低耦合度。
  • 刻意的冗余,如果只需要依赖其他组件中很小的功能,允许适度的冗余和代码重复可以避免引入依赖增加耦合。
  • 管理器类,通过分层,用更多层的抽象降低对底层实现的耦合
  • 回调、观察者和通知。这些手段提供了类似订阅、通知的功能,避免直接依赖。

总结: 好的API设计是有迹可循的,通过学习这些好的工程实践,有利于提高API设计的品味,阅读源码更加轻松,同时为自己设计API提供了好的参考。

Charles

Stay hungry, stay foolish.

文章评论