乌托邦式的接口和实现分离技术

2008-02-23 05:35:27来源:互联网 阅读 ()

新老客户大回馈,云服务器低至5折

  《Imperfect C 》中展示了一种叫“螺栓”的技术,然而,这本书中的讨论并不足够深入。当然,我也相信Matthew是故意的,从而让我们这些“三道贩子”(Matthew自称是二道贩子)也能够获得一点点成就感。

  考虑这样一个接口设计:

struct IRefCount;
struct IReader : public IRefCount;

  在Reader中实现接口:

<!--[if !supportEmptyParas]--> class Reader : public IReader;

  在上述的继承结构中,IRefCount是个结构性的类,用来实现引用计数,实际上他和领域逻辑部分IReader没有什么关系。我们打算在IRefCount的基础上,建立了一套工具来管理对象生命周期和帮助实现异常安全的代码 (例如,smart pointer) 。现在来考虑Reader的实现,Reader除了需要实现IReader的接口,还必须实现IRefCount的接口。这一切看起来似乎顺理成章,让我们继续看下面的设计<!--[if !supportEmptyParas]-->:

struct IWriter : public IRefCount;
<!--[if !supportEmptyParas]--> class Writer : public IWriter;

  现在来考虑Writer的实现,和Reader相同,Writer除了要实现IWriter的接口外,同时还需要实现IRefCount的接口。现在,我们来看看IRefCount是如何定义的:

struct IRefCount {
virtual void add() = 0;
virtual void release() = 0;
virtual int count() const = 0;
virtual void dispose() = 0;
virtual ~IRefCount(){}
};

  在Reader中的IRefCount的实现:

virtual void add() { m_ref_count;}
virtual void release() {--m_ref_count;}
virtual int count() const{return m_ref_count;}
virtual void dispose() { delete this;}

int m_ref_count;

  同样,在Writer的实现中,也包含了一模相同的代码,这违背了DRY原则(Don’t Repeat Yourself)。况且,随着系统中的类增加,大家都意识到,需要将这部分代码复用。一个能够工作的做法是把IRefCount的实现代码直接放到IRefCount中去实现,通过继承,派生类就不必再次实现IRefCount了。我们来看一下dispose的实现:

virtual void dispose() { delete this;}

  这里,采用了delete来销毁对象,这就意味着Reader必须在堆上分配,才可能透过IRefCount正确管理对象的生命周期,没关系,我们还能够override dispose方法,在Reader如下实现dispose:

virtual void dispose() { }

  但是,这样又带来一个问题,Reader不能被分配在堆上了!假如您够狠,当然,您也能够这么解决问题:

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.

  现在的问题是:

struct IRWiter : IReader, IWriter;

  还是

struct IRWiter : virtual IReader, virtual IWriter ?

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇: C 还能重新辉煌吗?C 复杂性的思考

下一篇: 重载、覆盖和隐藏 之细谈