继承与面向对象设计 virtual的替换方式

公有继承是一种 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 {...};
  1. 声明一个纯虚函数是为了让派生类只继承函数接口

    如draw函数,每个图形都需要draw函数,且每个图形的draw函数不同。

  2. 普通虚函数是为了让派生类继承函数接口与缺省实现

    如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);
	}
};
  1. non-virtual成员函数是给派生类接口与强制性实现

    non-virtual函数代表基类的一种不变性,绝对不应该在派生类中重写。

virtual函数的有趣替代

考虑一个游戏中的人物角色类,不同的类有不同的血量计算方法。

  1. 传统virtual方法
class GameCharacter {
public:
	virtual int healthValue() const;
};
  1. 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
	{
		... //提供缺省算法
	}
};
  1. 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;
};

法二至少有两个好处:

  1. 编译耦合度低,参见C++接口与实现分离 降低编译依存度的两种做法代码
  2. 如果Widget继续作为基类,法二可确保onTick函数不会被重写。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值