其他分享
首页 > 其他分享> > 使用经验 91 区分继承、模版还有组合

使用经验 91 区分继承、模版还有组合

作者:互联网

作为C++程序设计开发人员,可以考虑下面三个设计问题:

(1)设计一个描述队列类。你可能需要不同的类,因为每个队列处理的数据不同。例如,可能会员会有一个表示int的队列,同样也可能有一个表示string的队列,甚至还有表示string队列的队列等等。但是如要求你不用标准STL库,假设需设计的队列类型需具备下述操作方法:创建队列,销毁队列,将对象加入队列尾部,从头部获得对象,检查队列是否为空,那么你该如何设计呢?

(2)设计一个类型描述人。可以想象的,这也许要不同的类,甚至创建人的队列。每种类型的人都有不同的行为,例如学生要学习,工人要做工等等。但有一个共同点是:人可以创建和销毁。

(3)我们考虑人类的头部,眼、鼻、口、耳都是头的组成部分。眼可以看东西,鼻可以闻气味,口可以吃饭,耳可以听。那么你该如何设计呢?

这三个问题颇有相似之处,但却是完全不同的3种设计理念。本实用经验后续部分,将深入探索这三者的差异。

最佳实践

下面是队列模板的声明实现。

// 队列模板实现。
template <class T> 
class CQueue
{ 
	//定义队列的节点结构 
	struct NODE 
	{ 
	NODE<T>* next; 
	T data; 
	};

public: 
	CQueue();
	virtual ~ CQueue();

	//在队尾入队 
	void push(T e) 
	//在队头出队 
	T pop();
	//判断队列是否为空 
	bool empty(); 
private: 
	//指向头结点的指针。 front->next->data是队头第一个元素。
	NODE<T>*m_front; 
	//指向队尾(最后添加的一个元素)的指针
	NODE<T>* m_rear;  
}; 

队列实现了,但是人类怎么实现呢?为什么人的类实现,不适合模板呢?

前面说过,人有一个共同点:人可以创建和销毁。”。这意味着必须为每种类提供不同的行为实现。不能写一个函数来处理所有类型人的行为。我们可定制一个函数接口,让所有种类的人都实现它。这貌似就是继承啊。对的,这的确是继承。而且函数接口必须声明为一个纯虚函数。

class CPerson
 {
public:
	virtual ~ CPerson ();
	virtual void Work() = 0; 
};

人类的子类,比如Student和Worker,当然得重新定义继承而来的Work函数接口:

// 学生类实现
class CStudent: public CPerson
{
public:
	virtual void Work();
};
// 工人类实现
class CWorker: public CPerson
{
public:
	virtual void Work();
};

至此,我们知道了模板适合CQueue类,继承适合CPerson类。唯一剩下的问题是,为什么继承不适合CQueue类。要解决这个问题,不妨试着声明一个CQueue队列基类,所有其它的队列类,都从这个类继承:

class CQueue
{ 
public: 
	CQueue();
	virtual ~ CQueue();
	//在队尾入队 
	void push(const ??? e) 
	//在队头出队 
	T pop();
	//判断队列是否为空 
	bool empty(); 
private: 
	//指向头结点的指针。 front->next->data是队头第一个元素。
	NODE<T>* front; 
	//指向队尾(最后添加的一个元素)的指针 
	NODE<T>* rear;  
};

可很明显的看到,push函数的输入参数类型无法确定,数据的类型影响函数的行为,所以这种实现方式是有问题的。

明白了继承和模板的关系,我们继续讨论继承和组合的问题。上文我们讨论过继承描述的两类的关系是is kind of。而组合描述的是a part of的关系。

组合模式实现,眼(Eye)、鼻(Nose)、口(Mouth)、耳(Ear)是头(Head)的一部分,所以类Head 应该由类Eye、Nose、Mouth、Ear 组合而成,不是派生而成。

// 眼睛类
class Eye
{
public:
  void Look(void);
};
// 鼻子类
class Nose
{
public:
    void Smell(void);
};
// 嘴类
class Mouth
{
public:
  void Eat(void);
};
// 耳朵类
class Ear
{
public:
  void Listen(void);
};
// 头类的实现。
class Head
{
public:
	void Look(void)
 	{
 		m_eye.Look();
    }
    void Smell(void)
 	{ 
		m_nose.Smell(); 
	}
	void Eat(void)
 	{
 		m_mouth.Eat();
    }
    void Listen(void)
 	{
    	m_ear.Listen();
 	}
private:
  Eye m_eye;
  Nose m_nose;
  Mouth m_mouth;
   Ear m_ear;
};

如果允许Head 从Eye、Nose、Mouth、Ear 派生而成,那么Head 将自动具有Look、Smell、Eat、Listen 这些功能:

class Head : public Eye, public Nose, public Mouth, public Ear
{
};

上述程序十分简短并且运行正确,但是这种设计却是错误的。Head不是Eye,不是Nose….。这不符合public继承的基本原则。虽然这儿运行正确,但是无法保证所有情况下都能正确的运行。

总结

请谨记

标签:CQueue,队列,模版,区分,public,class,91,void,继承
来源: https://blog.csdn.net/liuguang841118/article/details/120892670