本文翻译自C++之父的CppCoreGuidelines部分章节:C.129
C++中的接口
C++中的接口一般设计为不包含成员变量的抽象类,这样就只有接口的功能。
如果接口类中包含了成员变量,那么势必会给派生带来麻烦。
不好的设计:接口与实现混合
class Shape { // BAD, mixed interface and implementation
public:
Shape();
Shape(Point ce = {0, 0}, Color co = none): cent{ce}, col {co} { /* ... */}
Point center() const { return cent; }
Color color() const { return col; }
virtual void rotate(int) = 0;
virtual void move(Point p) { cent = p; redraw(); }
virtual void redraw();
// ...
public:
Point cent;
Color col;
};
class Circle : public Shape {
public:
Circle(Point c, int r) :Shape{c}, rad{r} { /* ... */ }
// ...
private:
int rad;
};
class Triangle : public Shape {
public:
Triangle(Point p1, Point p2, Point p3); // calculate center
// ...
};
缺点:
1 给Shape增加数据成员,所有派生类都要多出这个成员,不管是否需要。而且都要重新编译。
2 有的派生类并不需要Shape中的某些数据成员,但也不得不接受。
接口示例:纯接口类的Shape
class Shape { // pure interface
public:
virtual Point center() const = 0;
virtual Color color() const = 0;
virtual void rotate(int) = 0;
virtual void move(Point p) = 0;
virtual void redraw() = 0;
// ...
};
无须构造,因为没有数据成员
简单实现类的示例:
class Circle : public Shape {
public:
Circle(Point c, int r, Color c) :cent{c}, rad{r}, col{c} { /* ... */ }
Point center() const override { return cent; }
Color color() const override { return col; }
// ...
private:
Point cent;
int rad;
Color col;
};
但是,这样以来,实现类的成员函数就变得繁琐,因为每个实现类都要实现一边接口的成员函数。
将接口和实现分离:
1 接口继承:
class Shape { // pure interface
public:
virtual Point center() const = 0;
virtual Color color() const = 0;
virtual void rotate(int) = 0;
virtual void move(Point p) = 0;
virtual void redraw() = 0;
// ...
};
class Circle : public Shape { // pure interface
public:
int radius() = 0;
// ...
};
2 实现继承:实现放入名称空间Impl中
下面是实现(因为每个函数后面都有大括号),实现放到了Impl这个名称空间中了,这样使用Shape的客户端代码就不会随着Impl中Shape类的成员变量的变化而重新编译,因为客户端代码是看不到这些内容的,编译的时候也没有绑定这些信息。
class Impl::Shape : public Shape { // implementation
public:
// constructors, destructor
// ...
virtual Point center() const { /* ... */ }
virtual Color color() const { /* ... */ }
virtual void rotate(int) { /* ... */ }
virtual void move(Point p) { /* ... */ }
virtual void redraw() { /* ... */ }
// ...
};
这样一来接口变化只是成员函数的变化,成员变量的增减就看不到了,因为成员变量在Impl::Shape中
考虑派生类的写法:派生类也和上面一样提供了实现类,用户只使用接口类和包含实现类的实现的DLL,从而不受实现类变换的影响,不需要重新编译。可以直接替换DLL
class Impl::Circle : public Circle, public Impl::Shape { // implementation
public:
// constructors, destructor
int radius() { /* ... */ }
// ...
};
考虑新增派生类的写法:
class Smiley : public Circle { // pure interface
public:
// ...
};
class Impl::Smiley : Public Smiley, public Impl::Circle { // implementation
public:
// constructors, destructor
// ...
}
两条派生路线:
Smiley -> Circle -> Shape
^ ^ ^
| | |
Impl::Smiley -> Impl::Circle -> Impl::Shape
参考:https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md