假如您没有选择virtual,那么IRWiter被派生后,那么派生类的继承树中可能存在多个IReader实现,假如这个派生类需要只能提供一份IReader的语义怎么办?除了重新实现接口还能怎样?反过来,假如我们选择了virtual继承,那么派生类需要多个实现怎么办?真是个麻烦事。“这是典型的过度设计,我们为什么要考虑这么多?”您能够这么说,但事实上,即使是个数百文档的小型系统,也完全可能迫使您作出选择。虽然,我们仍然有办法作出挽救措施,但是也只是苟延残喘而已。正如我前面所说,这是个危险的道路,聪明如您,是断然不会让自己陷入这样的泥潭的。
让我们离开虚拟继承,先回到重复代码的问题上来。有没有更好的解决办法呢?还好,在C 的世界里,我们有神奇的template,让我们来消除重复的代码:
| template<typename Base> class ImpReader : public Base{ constraint(is_base_derive(IReader, Base)) Implementation IReader <!--[if !supportEmptyParas]--> }; class HeapReader : ImpReader<IReader>{}; class StackReader : ImpReader <IReader>{ virtual void dispose() {}; <!--[if !supportEmptyParas]--> }; |
请注意,我们还是假设IRefCount已提供了一个默认实现。现在,情况好了很多,任何的代码都只有一份,而且,概念也没有被破坏。假设,Writer也同样需要类似的能力,那么,我们又多了StackWriter和HeapWriter.事实上,真的有人用到了StackWriter吗?我不知道,只是,提供了StackReader,没有理由不提供StackWriter啊。让我们继续。
现在,我们发现,需要改进内存分配的性能问题,于是,我们希望通过内存池来分配对象,相应的dispose也需要修改:
| virtual void dispose(){ distory(this);} |
于是,我们又多出两个类,PoolReader和PoolWriter。这真是糟糕,组合爆炸可不是什么好兆头。
从我们前述的变化来看,都是IRefCount在变化,为什么不把这种变化分离出来呢?不必为IRefCount提供默认实现,借鉴ImpReader的手法:
| 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.
| typedef ImpReader< ImpHeapRefCount<IReader> > HeapReader; typedef ImpReader< ImpStackRefCount<IReader> > StackReader; typedef ImpReader< ImpPoolRefCount<IReader> > PoolReader; |
以HeapReader为例,实际的继承关系是这样的:
| ImpReaderàImpHeapRefCountàIReaderàIRefCount; |
对于Writer,我们完万能够采取同样的手法来实现。对于上述的typedef能够预先定义,也完万能够不定义,交给最终用户去组装吧。现在,类的设计者再也不必为选择实现而痛苦了,您只要提供不同的砖头,客户程式员能够轻而易举的建立起大厦。更有比这更让一个设计师幸福的吗?
继续深入,考察ImpHeapRefCount和ImpStackRefCount的实现,我们提到,dispose方法的实现是不相同的,但是,其他部分:add,releasee和count的实现完万能够相同。然而我们现在又分别实现了一遍,为了不违背DRY原则,我们如下处理:
| template<typename Base> class ImpPartialRefCount : public Base{ //实现add, release和count. }; template<typename Base> class ImpHeapRefCount : public Base{ virtual void dispose() { delete this;} }; template<typename Base> class ImpStackRefCount : public Base{ virtual void dispose() { } }; |
然后,我们能够这样定义Reader:
| typedef ImpReader<ImpHeapRefCount<ImpPartialRefCount<Ireader> > > HeapReader; |
请注意,我们在这里展示了一种能力,不必在一个实现当中完整的实现整个接口,能够把一个接口的实现分拆到多个实现当中。这个能力是非凡的,借助于此,我们能够提供更小粒度的实现单位,给最终用户来组装。具体拆分到什么样的程度完全取决于代码复用的需求,连同概念维护的需要。
我们提供了高度的复用能力,同时避免了继承带来的强耦合,连同对推迟设计决策的支持,这些能力对于软件设计师而言,正如Matthew在《Imperfect C 》中所说的,这简直就是现实中的乌托邦!
现在我们把这种手法首先针对单继承做一个小结。对于任意的接口IInterface,我们提供如下的实现:




