工程实践:C++的接口设计
最近在工作中,需要将代码封装成库,供其他方调用。在其中涉及到如何设计接口类,第一次接触,将总结和经验记录下来。
导读
为什么本文叫做《工程实践:C++的接口设计》,是因为,我们大部分人入门的时候,都是调用别人封装好的库函数,却没有尝试过自己封装库给别人用。但是在正常工作中,也就是工程化中,我们会经常封装库给其他人应用,这里面会涉及到怎么封装一个函数,提供一个优秀的接口。
接口:类暴露出来的部分,是类所提供的功能。
接口设计准则
我们在工程化的过程中,设计接口一般遵守以下几个准则:
- 单一功能原则
一个class就其整体应该只提供单一的服务。如果一个class提供多样的服务,那么就应该把它拆分,反之,如果一个在概念上单一的功能却由几个class负责,这几个class应该合并。 - 开放/封闭原则
一个设计并实现好的class,应该对扩充的动作开放,而对修改的动作封闭。也就是说,这个class应该是允许扩充的,但不允许修改。如果需要功能上的扩充,一般来说应该通过添加新类实现,而不是修改原类的代码。添加新类不单可以通过直接继承,也可以通过组合。 - 最小惊讶原理
在重载函数,或者子类实现父类虚函数时,应该基本维持函数原来所期望的功能。
接口设计注意事项
- 过度封装
很多人喜欢这样封装,把接口局限在仅仅解决一个特定的问题上面,失去了代码的灵活性。而且,也容易出现面条代码,让人不知所云。一个接口被写的仅仅用于解决当前问题,当试图增加其扩展性时,发现为时已晚。
为了防止过度封装,在设计接口的时候,我们应该考虑以下几个问题:
1.我们需要解决什么问题
2.问题的核心是什么
3.应该怎样设计,可以方便客户程序员扩展
-
起名要见名知意
一个好的的接口方法名,是接口设计中成功的一半。 -
不要让使用者进行过多工作
如果使用者使用我们的接口时,进行了过多的准备,那么对我们来说就是失败的。
所以,设计的时候要记得尽可能简化客户程序员逻辑,使接口设计能够看起来简洁、漂亮,而不至于被接口的复杂性所吓倒。 -
简洁
这个比较好理解,举个例子,你一定见过一个函数使用,需要传进去五六个参数,但是对我们有用的往往只有那么一俩个。
所以,简洁设计是接口设计的一个重要原则。可以在接口的内部实现中,使用复杂冗余的参数,而在暴露给客户程序员的接口中,一定要尽可能简洁。 -
清晰的文档表述
这也是我在工作中,最头疼的问题,头疼的不是我不会写,而是在使用其他人提供的库时,没有使用文档,使用起来是痛苦的,所以为了不让这种痛苦发生在其他人身上,我现在从维护一份良好的文档开始。
接口设计想达到的效果
隔离用户操作与底层逻辑
接口的俩种方法
一般来说,有两种方法设计接口类。
第一种是PIMP方法,即Pointer to Implementation,在接口类成员中包含一个指向实现类的指针,这样可以最大限度的做到接口和实现分离的原则。
第二种方法叫Object-Interface方法,它的思想是采用C++的动态功能,实现类继承接口类,功能接口函数定义成虚函数。
先说结论,我们处于自身习惯的原因,选择了Object-Interface方法。
PIMP方法
所谓PImp是非常常见的隐藏真实数据成员的技巧,核心思路就是用另一个类包装了所要隐藏的真实成员,在接口类中保存这个类的指针。
//header complex.h
class ComplexImpl;
class Complex{
public:
Complex& operator+(const Complex& com );
Complex& operator-(const Complex& com );
Complex& operator*(const Complex& com );
Complex& operator/(const Complex& com );
private:
ComplexImpl* pimpl_;
};
在接口文件中声明一个ComplexImpl*,然后在另一个头文件compleximpl.h中定义这个类
//header compleximpl.h
class ComplexImpl{
public:
ComplexImpl& operator+(const ComplexImpl