1 API简介
-
API(Application Programing Interface)提供了对某个问题的抽象,以及客户与解决改问题的软件组件之间进行交互的方式。组件本身通常以软件类库形式分发,它们可以在多个应用程序中使用。概括说,API定义了一些可复用的模块,使得各个模块化功能可以嵌入到最终用户的应用程序中去。 API是一个明确定义的接口,可以为其他软件提供特定服务。
-
C++API通常会包含如下的元素:
头文件:一组.h头文件,头文件定义了接口,使得客户端代码能够针对该接口进行编译 开源API还包括API实现的源代码即.cpp文件
**类库:**一个或者多个静态库或者动态库文件,它们提供了API的具体实现 客户端把代码和这些库文件进行链接,从而为应用程序添加相应功能
**文档:**使用API的概述信息
在C++中,API一般包括一个或多个头文件(.h)以及辅助文档。某个特定API的具体实现通常是可以被链接到最终用户程序中的库文件,它也可以是静态库,又或者是动态库。
- SDK与API:
SDK(软件开发工具包)和术语API是密切相关的。本质上讲,SDK是安装在计算机上的特定平台的包,其目的是使用一个或多个API构建应用。SDK至少要包含编译程序所需的头文件(.h)以及提供API实现的库文件(.dylib、.so、.dll),用以链接到应用程序之中。然而,SDK还可能包含其它帮助使用API的资源,如文档、示例代码以及支持工具。
可以把SDK想象成一个虚拟的程序包,在这个程序包中有一个做好的软件功能,这和程序包有一个接口可以被调用,这个接口就是API
- 文件格式和网络协议
在计算机应用中存在几个其它形式的常用通信”协议”,其中最常见的一个就是文件格式。它是使用众所周知的数据组织层次将内存中的数据存储到磁盘文件上的方法。有了数据文件的格式,任何程序都能读写这种格式的图像文件。这就使得不同用户之间可以交换图像数据
在客户端/服务端应用、点对点应用以及中间件服务,使用建立好的且通常基于网络套接字的协议发送和接收数据。
每当创建一个文件格式或者客户端/服务器协议时,同时也要为其创建API。这样,规范的细节以及未来的任何变更都将是集中且隐藏的。
2 特征——优秀的API应该具有哪些特征
-
问题域建模:编写API的目的是解决特定的问题或完成具体的任务; API应该对它所解决的问题提供逻辑抽象, 当你把API文档提供给一位非程序员时,他应当能够理解接口中的概念并且知道它的工作机制; 对问题域的关键对象建模, 该过程旨在描述特定问题域中对象的层次结构,因此经常被称作”面向对象设计”或者”对象建模”。对象建模的目的是确定主要对象的集合,这些对象提供的操作以及对象之间的关系。
-
隐藏实现细节:创建API的主要原因是隐藏所有的实现细节,以免将来修改API对已有客户造成影响。任何内部实现细节(那些很可能变更的部分)必须对该API的客户隐藏。主要有两种技巧可以达到此目标:物理隐藏和逻辑隐藏。物理隐藏表示只是不让用户获得私有源代码。逻辑隐藏则需要使用语言特性限制用户访问API的某些元素。
物理隐藏:在C和C++中,声明只是告诉编译器一个名字以及它的类型,并不为其分配任何内存;而定义提供了类型结构体的细节,如果是变量则为其分配内存;声明告诉编译器某个标识符的名称以及类型,定义提供该标识符的完整细节 物理隐藏表示将内部细节(.cpp)与公有接口(.h)分离,存储在不同的文件中
逻辑隐藏:封装提供了限制访问对象成员的机制:public、protected、private。封装是将API的公有接口与底层实现分离的过程。逻辑隐藏指的是使用C++语言中受保护的和私有的访问控制特性从而限制访问内部细节。类的数据成员应该始终声明为私有的,而不是公有的或受保护的。
永远不要返回私有数据成员的非const指针或引用,这样会破坏封装性
强烈建议在API中采用Pimpl惯用法,这样就可以将所有实现细节完全和公有头文件分开。
Pimpl惯用法:它将所有的私有数据成员隔离到一个.cpp文件中独立实现的类或结构体中。之后,.h文件仅需要包含指向该类实例的不透明指针(opaque pointer)即可。
如果不用pimpl,至少也要将头文件内不需要的私有方法移到.cpp中,并将它们转换为静态函数。不要将其作为私有方法暴露在公开的头文件中
隐藏实现类:除了隐藏类的内部方法和变量之外,还应该尽力隐藏那些纯粹是实现细节的类。实际上,一些类仅用于实现,因此应该将其从API的公有接口中移除。
-
最小完备性:尽量简洁
谨慎添加虚函数: 虚函数的调用必须在运行时查询虚函数表决定,而普通的非虚函数的调用在编译时就能确定。使用虚函数一般需要维护指向虚函数表的指针,进而增加了对象的大小。另外,不是所有的虚函数都能内敛,因而将虚函数声明为内联是没有任何意义的。因为虚函数是运行时确定的,而内联是在编译时进行优化的,且C++中inline只是一个提示,不一定优化的;如果类中包含任一虚函数,那么必须将析构函数声明为虚函数,这样类才能释放其他可能申请的额外资源。绝对不要在构造函数或析构函数中调用虚函数,这些调用不会指向子类
-
易用性
1 正交:API之间互不影响
2 使用智能指针返回动态申请的对象使得资源分配更见状
3 使用一致的函数命名和参数顺序
4 不要将平台相关的#if或#ifdef语句放在公共的API中,因为这些语句暴露了实现细节,并使API因平台而异 P43
-
松耦合
1 除非确实需要#include类的完整定义,否则应该为类使用前置声明。如果类**A仅需要知道类B的名字,即它不需要知道类B的大小或调用类B的任何方法,**那么类A就不需要依赖类B的完整声明。在这种情况下,可以为类B使用前置声明,而非包含整个接口,这样就降低了这两个类之间的耦合 (就是前置只写class B;就好,不需要#include “B.h”)
2 回调:在C和C++中,回调是模块A中的一个函数指针,该指针被传递给模块B,这样B就能在合适的时候调用A中的函数。模块B对模块A一无所知,并且对模块A不存在”包含”(include)或者”链接”(link)依赖。回调的这种特性使得低层代码能够执行与其不能有依赖关系的高层代码。因此,在大型项目中,回调是一种用于打破循环依赖的常用技术。p51