C++ API 设计(API design for C++)笔记by 浅奕

第一章 API简介

  1. API(Application Programming Interface )提供了对某个问题的抽象,以及客户与解决该软件问题的软件组件之间进行交互的方式。组件本身通常以软件类库的形式分发,它们可以在多个应用程序中使用。概括地说,API定义了一些可复用的模块,使得各个模块可以嵌入到最终用户的应用程序中去。

  2. C++中的API通常会包含以下元素:头文件、类库、文档。

第二章 特性

API的特征:

  • 问题域建模(提供良好的抽象、关键对象的建模)
  • 隐藏实现的细节(物理隐藏:声明与定义,逻辑隐藏:封装,隐藏成员变量,隐藏实现方法,隐藏实现类)
  • 最小完备性(不要过度承诺、谨慎添加虚函数、便捷API)
  • 易用性(可发现性、不易误用、一致性(比如命名格式,参数顺序,内存模型的语义,错误处理等)、正交(函数间没有副作用)、健壮的资源分配、平台独立(接口一致))
  • 松耦合(仅通过名字耦合(类的前置声明),降低类耦合(优先声明非成员、友元函数),刻意的冗余(稍微冗余两个类之间共有的功能而不是抽象出来增加耦合度),管理器类(工厂模式),回调(lamada、仿函数、闭包等等)、观察者(观察者模式)和通知(boost的信号与槽))。

不使用public成员而使用private成员+getter和setter惯用法的原因:

  • 有效性验证(可以在setter里检查设置的值是否在许可区间里)
  • 惰性求值(比如一个成员计算过于耗时,而这个类的用户(这里的用户指其他程序员)不一定需要时,可以在getter方法调用的时候再计算)
  • 缓存
  • 额外的操作(比如用户调用setter方法时,可以把这个值更新到配置文件里)
  • 通知(其它模块可能需要在某个值发生变化的时候做一些操作,那么就可以在setter里实现)
  • 调试(可以方便的打印设置日志,从而追踪错误)
  • 同步(如果多线程访问需要加锁的话,setter里加锁不是很容易么)
  • 更精细的权限访问(比如private变量只有getter没有setter,那客户对该变量就是只读了,而类的内部代码可以读写)
  • 维护不变式关系(比如一个类内部要维持连个变量a和b有a = b * 2的关系,那么在a和b的setter里计算就能维持这样的关系)

个人觉得还有一个:

  • 便于类升级修改(getter/setter不向类的用户保留类内部的数据结构组织方式,那么在类内部的数据结构发生改变时,无需修改使用该类的代码)

第三章 模式

模式分类(按照GOF提出的23种)

  • 创建型模式 抽象工厂模式、建造者模式、工厂方法模式、原型模式、单例模式
  • 结构型模式 适配器模式、桥接模式、组合模式、装饰模式、外观模式、享元模式、代理模式
  • 行为模式 职责链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模版方法模式、访问者模式

以下技巧详述:

  • Pimpl惯用法 —— 在公有接口中完全隐藏内部细节
  • 单例和工厂方法 —— 确保只创建一个对象和隐藏派生类实现细节
  • 代理、适配器和外观 —— 改善现有API接口的各种方法
  • 观察者 —— 减少类之间的直接依赖

Pimpl惯用法

Pimpl即 Pointer to implementation 的简写,是受制于C++特性的一种特例。本质上讲,即是将私有的成员变量和函数转移到cpp文件中的方法。具体的做法是公有类持有一个私有指针,该指针指向隐藏的实现类。

|-----------------|
|    公有接口      |
|-----------------|
|public:          |
|  function1();   |                       |----------------|
|  function2();   |                       |    私有实现     |
|-----------------|                       |----------------|
|protected:       |                       |private:        |
|  function3();   |                       |  function1();  |
|-----------------|                       |  function2();  |
|private:         |                       |  data1;        |
|  Impl *mlmpl; ------------------------> |  date2;        |
|-----------------|                       |----------------|

要小心C++的复制语义,需要自己实现拷贝构造函数和重载赋值操作了

优点:信息隐藏、降低耦合、加速编译、更好的二进制兼容性 缺点:引入性能冲击、代码复杂

单例

单例用来确保一个类只产生一个实例化的对象,相比全局变量的有点如下:

  • 确保一个类只创建一个实例
  • 为对象分配和销毁提供控制
  • 支持线程安全的访问对象的全局状态
  • 避免污染全局命名空间

简单做法是将构造函数,析构函数,拷贝构造函数、赋值操作符设为私有,提供静态共有的方法返回唯一对象的引用。

C++中,不同编译单元中的非局部静态对象的初始化顺序是未定义的。

我们可以这样:

Singleton &Singleton::getInstance() 
{
	static Singleton instance;

	return instance;
}

但是,上边的代码不是线程安全的!如果两个线程同时调用这个函数,实例就有可能被构造两次。奇葩的方法就不说了,陈硕的书中给出了一个实现,在muduo库中的实现是通过linux下的pthread_once函数实现的(不可移植)。

工厂方法、代理、适配器和外观,观察者

这些设计模式还是去系统的学学设计模式相关的书籍吧~~

第四章 设计

待续…

Published 27 July 2014