条款24:使用/滥用继承(Uses and Abuses of Inheritance)

以下template提供了list的管理功能,包括在特定位置上的处理元素的功能:

//Example 1
template<class T>
class Mylist
{
public:
	bool Insert(const T&,size_t index);
	T Access(size_t index) const;
	size_t Size() const;
	
private:
	T* buf;
	size_t bufsize_;
};

考虑如下的代码,其中实现以MyList为基础,实现一个MySet class的不同做法。假设所有的重要元素都已实现:

//例1(a)
template<class T>
class MySet1 : private MyList<T>
{
public:
	bool Add(const T&); //call insert
	T Get(size_t index) const; //call Access
	using MyList<T>::Size;
	//...
};

//例1(b)
template <class T>
class MySet2
{
public:
	bool Add(const T&); //call impl_.insert
	T Get(size_t index) const; //call impl_.Access
	size_t Size() const;	//call impl_.Size
	//...

private:
	MyList<T> impl_;	//containment
};

请思考如下的问题:

  1. MySet1和MySet2之间的任何差异?
  2. 广泛的说nopublic inheritance 和containment有什么不同?说明你为什么采用inheritance而不采用containment。
  3. 你比较喜欢哪一个版本?
  4. 尽可能的说明你为什么喜欢public inheritance。

【解答】

1.答案:两者之间不具有实际意义的差别,它们的机能完全相同。

2.答案:

  • Nonpublic inheritance应该总是表现根据某物实现(IS-IMPLEMENT-IN-TERMS-OF)的意义,它使用using 只能依赖class 的public/protected部分。
  • Containment总是表现HAS-A的意义,连带有根据某物实现(IS-IMPLEMENT-IN-TERMS-OF)的意义。它使用using containment只能依赖class的public部分。

容易看出,inheritance是single containment的一个超集,也就是说,没有什么可以用一个MyList<T> member实现的而却无法以继承自MyList<T>完成的事。当然了,inheritance造成只能拥有一份MyList<T>(作为base subobject):如果我们想拥有多份MyList<T>实体,就必须改用containment。

设计准则:尽量采用aggregation(又名“composition”,“layering”,“HAS-A”,“delegation”)来取代inheritance。当我们模塑IS-IMPLEMENTED-IN-TERMS-OF关系时,尽量采用aggregation而不要使用inheritance。

如下是使用nopublic inheritance的理由:

  • 我们需要改写一个虚函数。
  • 当我们需要处理protected member。
  • 当我们需要在一个base subobject之前构建used object。
  • 当我们需要分享某种共享的virtual base class或改写virtual base class的构造程序。
  • 当我们从empty base class的最佳化获益时。
class B{/*只有函数,没有数据*/};
//包含:有空间开销

class D
{
	B b_;	//b_必须占一个字节
			//即使B是空类
};

class D : private B
{
	//B子对象无须占用任何内存
};
  • 当我们需要“受控的多态性(controlled polymorphism)”——LSP IS-A。public inheritance总是模塑IS-A的关系,nonpublic inheritance可以表现受限制的IS-A关系。虽然大部分人都是以public inheritance来识别IS-A。

3.答案:

  • Mylist没有保护成员,我们不需要访问它们。
  • Mylist没有虚函数,我们不需要继承它们并改写它们。
  • Myset没有其他潜在的base classes。所以Mylist不需要再另一个base subobject之前构建和之后析构。
  • Mylist没有任何的virtual base classes是Myset可需要共享的,或construction可能需要改写的。
  • Mylist不是空类,所以empty base class最优化动机并不适用。
  • Myset不是一个(IS-NO-A)Mylist,甚至不再Myset的成员函数和友元函数中。

简单的来说,Myset不应该继承Mylist。在containment同样有效的情况下使用inheritance,只会引入无偿的耦合性和不必要的依赖性。

如果要改写一个虚函数如Func1或存取一个protected member如Func2,就必须采用inheritance不可,但是下面的做法是不需要的吗?

//例2(a)
class B
{
public:
	virtual int Func1();
protected:
	bool Func2();
private:
	bool Func3();	//使用Func1
};

class Derived : private Base
{
public:
	int Func1();
	//更多函数,其中一些使用了Base::Func2
};

这段代码允许Derived改写Base::Func1,这很好。不幸的是,它也允许让所有的Derived members使用Base::Func2,这就是问题所在。通过这样使用继承,我们所有的Derived的成员都依赖于Base的保护接口,这大可不必要。

下面的做法更加好:

//2(b)
class DerivedImpl : private Base
{
public:
	int Func1();
	//...这里的函数需要使用Func2
};
class Derived 
{
	//...这里的函数不需要使用Func2
private:
	DerivedImpl* impl_;
};

这个设计好多了,因为它很好的隔离和封装了对于Base的依赖性。Derived只能直接依赖Base的共有接口和DerivedImpl的公有接口。设计成员的原因:遵循一个类,一个职责的设计原则。

现在看看containment的优点:第一,它允许我们永远多个used class的实例,这对inheritance而言不可能。

如果你继续派生有需要多份实例,请使用2(b)所展示的方法:派生出一个辅助类(DerivedImpl)负责继承的工作,然后再再使用这个辅助类的多个实例。

第二,它令used class成为一个data member,这样带来更大的弹性。那个member可以被隐藏在Pimpl内部的编译器防火墙之后,而且有必要在运行时改变的话,可以轻易的被转换成指针(尽管继承体系是静态并在编译器就固定了)。

最后更加1(b)重写Myset2的第三种有效方法,它以一种更普遍的方式来使用包含:

//例1(c),泛型包含
template <class T,class Impl = Mylist<T> >
class MySet3
{
public:
	bool Add(const T&); //调用 impl_.Insert();
	T Get(size_t index);//调用impl_Access();
	size_t Size() const;//调用impl_Size();
	//...
private:
	Impl impl_;
};

现在我们有了更加的弹性,不再只是以Mylist<T>来实现,更可以令Myset根据任何class为基础来实现——只要它们支持Add,Get以及其他需要的函数。

4.答案:

public inheritance用来模塑真正的IS-A关系,一如里氏替换原则所说:子类即基类,只要可以使用base class object的地方,就应该可以使用publicly derived class object。

遵循以下规则可以避免常见的一些易犯的错误:

  • 如果nopublic inheritance能用的情况下,就绝对不要考虑public inheritance。
  • 如果class相互关系可以以多种方式来表现,请使用最弱的一种关系。

不要被诱惑:如果其行为不像基类,它就不是一个Base,那就不要以(使它看起来像个Base)的方式来派生它。

设计准则:总是确定public inheritance用来模塑IS-A和WORK-LIKE-A的关系,并符合里氏替换原则。所有被改写的member function都必须不要求更多,也不承诺更少。

设计准则:绝对不要为了重用base class中的代码而使用public inheritance。public inheritance的目的是为了多态方式重用base object。

结论:

明智的运用inheritance。如果你单独以containment/delegation表现某个class相互关系,你就应该这么做。如果你需要inheritance但不想模塑出IS-A关系,请使用nopublic inheritance。如果你不需要多重继承的威力,请使用单一继承。一般而言,大而深的继承体系特别难理解,因此也就难以维护。inheritance是一个设计期决定,必须舍去许多运行期的弹性。

一些人以为,除非使用继承体系,某则算不上面向对象设计,这实际上是不对的。尽可能使用可有效的解答中最简单的一个,那么你还可能多享受几年稳定而容易维护的代码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值