struct IWriter : public IRefCount; <!--[if !supportEmptyParas]--> class Writer : public IWriter; 现在来考虑Writer的实现,和Reader一样,Writer除了要实现IWriter的接口外,同时还需要实现IRefCount的接口。现在,我们来看看IRefCount是如何定义的:
class HeaPReader : IReader; class StackReader : HeapReader{ virtual void dispose() { } }; 问题是,StackReader 是一个HeapReader吗?为了代码复用,我们完全不管什么概念了。当然,假如你和我一样,看重维护概念,那么这么实现吧:
class HeapReader : IReader; class StackReader : IReader; 这样一来,IReader的实现将被重复,又违反了DRY原则,等着被将来维护的工程师诅咒吧!或许,那个维护工程师就是3个月后的你自己。假如这样真的能够解决问题,那么也还是可以接受的,很快,我们有了一个新的接口:
struct IRWiter : IReader, IWriter; class RWiter : public IRWiter; 考虑一下IRefCount的语义:它用来记录对所在对象的引用计数。很显然,我从IReader和IWriter中的任意一个分支获得的IRefCount应该都是获得一样的引用计数效果。但是现在,这个继续树存在两个IRefCount的实例,我们不得不在RWiter当中重新重载一遍。这样,从IReader和IWriter继续来的两个实例就作废了,而且,我们可能还浪费了8个字节。为了解决这个问题,我们还可以在另一条危险的道路上继续前进,那就是虚拟继续:
struct IReader : virtual public IRefCount; struct IWriter : virtual public IRefCount; 还记得大师们给予的忠告吗--“不要在虚基类中存放数据成员”。“这样有什么问题吗,我们不必对大师盲目崇拜”,你一定也听过这样的建议。假如大师们不能说服这些人,那么我也不能。于是,我们进一步在所有的接口中提供默认实现,包括IReader和IWriter.
template<typename Base> class ImpHeapRefCount : public Base{ constraint(is_base_derive(IRefCount, Base)); ..}; 类似的:
template<typename Base> class ImpStackRefCount : public Base; <!--[if !supportEmptyParas]--> template<typename Base> class ImpPoolRefCount : public Base; <!--[endif]--> 再看看,我们如何实现所有的Reader.