公有继承是一种 is-a 关系
典例一:
class Person{...};
class Student : public Person {...};
典例二:
class Bird{
public:
virtual void fly();
...
};
class penguin : public Bird{
...
};
企鹅是一种鸟,这是事实;鸟会飞,也是事实。但在代码这里并不合适,因为企鹅并不会飞。
可以如下纠正:
//method 1
class penguin : public Bird{
public:
virtual void fly(){ error("Attempt to make a penguin fly"); }
...
};
//method 2
class Bird{ //不声明fly函数
public:
...
};
class penguin : public Bird{ //也不声明fly函数
...
};
class flyingBird : public Bird{
virtual void fly() ;
};
public 继承是一种 is - a 关系,最好确保基类的所有方法都可以被派生类使用。
让派生类部分继承基类的方法
使用转交函数:
class Base {
public:
void mf1();
void mf2();
};
class Derived : private Base{
public:
void mf1() {Base::mf1(); }
};
区分接口继承和实现继承
class Shape {
public:
virtual void draw() const = 0;
virtual void error(const string& msg) { cout << msg << endl; }
int objectID() const;
};
class Rectangle : public Shape {..};
class Ellipse : public Shape {...};
-
声明一个纯虚函数是为了让派生类只继承函数接口
如draw函数,每个图形都需要draw函数,且每个图形的draw函数不同。
-
普通虚函数是为了让派生类继承函数接口与缺省实现
如error函数,每个图形都需要进行错误处理,如果不想重写一个,可以继承基类的缺省实现。
但是用这种方法,如果我们需要重写error函数却忘记了,那代码也可以正常编译,带来调试的难题。
可使用给虚函数提供定义的方法进行改进:
class Shape {
public:
virtual void error(const string& msg) = 0;
};
void Shape::error(const string& msg) { cout << msg << endl; }
class Rectangle : public Shape {
public:
void error(const string& msg)
{
//自主改写
...
//缺省实现
Shape::error(msg);
}
};
-
non-virtual成员函数是给派生类接口与强制性实现
non-virtual函数代表基类的一种不变性,绝对不应该在派生类中重写。
virtual函数的有趣替代
考虑一个游戏中的人物角色类,不同的类有不同的血量计算方法。
- 传统virtual方法
class GameCharacter {
public:
virtual int healthValue() const;
};
-
NVI(non - virtual - interface) 手法
这种手法就是给virtual函数套上一个wrapper,算是non-virtual 与 virtual的合并使用。
是一种特殊的Template Method设计模式。
class GameCharacter {
public:
int healthValue() const
{
...事前工作
int ret = doHealthValue();
...事后工作
return ret;
}
private:
virtual int doHealthValue() const
{
... //提供缺省算法
}
};
-
Strategy模式
借由函数指针或者function模板实现,这里使用Function模板,它的本质就是人工模拟virtual运行时多态的过程,好处是使得同一个类的不同对象也可以使用不同的方法。
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter {
public:
typedef function<int (const GameCharacter&)> HealthCalcFunc;
explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)
: healthFunc(hcf) {}
int healthCalc() const { return healthFunc(*this); }
private:
HealthCalcFunc healthFunc;
};
复合是一种 has - a 或“利用某物实现出”的关系
我们的软件设计两个领域。在现实应用领域,复合表现出has - a 的关系,在编码实现领域,复合表现出“利用某物实现出”的关系。
它的价值在于代码复用。
此外,private继承也是一种“利用某物实现出的”关系。在使用时尽可能使用复合,必要时才使用private继承
以实现一个带有定时器Timer的Widget为例:
我们希望利用这个工具类:
class Timer {
public:
explicit Timer(int tickFrequency);
virtual void onTick() const;//定时器每嘀嗒一次,就执行该函数
};
法一:private继承然后重新onTick函数
class Widget : private Timer {
private:
virtual void onTick() const; //重写多态函数
};
法二:利用复合
class WidgetTimer : public Timer {
public:
virtual void onTick();
};
class Widget {
private:
WidgetTimer* timer;
};
法二至少有两个好处:
- 编译耦合度低,参见C++接口与实现分离 降低编译依存度的两种做法代码
- 如果Widget继续作为基类,法二可确保onTick函数不会被重写。