手机站
网通分站
电信主站
密 码:
用户名:
当前位置 : 主页>程序设计>C/C++>列表

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

来源:互联网 作者:west263.com 时间:2008-02-23
西部数码-全国虚拟主机10强!40余项虚拟主机管理功能,全国领先!双线多线虚拟主机南北访问畅通无阻!免费赠送企业邮局,.CN域名,自助建站480元起,免费试用7天,满意再付款! P4主机租用799元/月.月付免压金!


struct IHttpReader : IReader;
struct IFileReader : IReader;

  我们需要一个对象,同时支持从网络和从文档读取的能力。先看不引入Combine的做法:

struct IFileHttpReader : IFileReader , IHttpReader;
typedef Reform<IFileHttpReader, ImpRefCount, ImpHttpReader,
ImpFileReader>::type ConcreteRWiter;

  觉得有什么问题吗?ImpReader同时实现了IFileReader分支和IHttpReader分支中的IReader,但是,和IRefCount不同的是,我们完全有理由相信,这两个分支其实需要不同的IReader的实现。即使IReader确实能够是同样的实现,另一个严重的问题是,ImpReader是个不完整的实现,ImpFileReader和ImpHttpReader都分别重载了IReader中的一部分方法,例如,两者都实现了如下方法:

virtual bool open(const char* url);

  如何解决这个问题?让我们回顾一下IFileHttpReader,首先这个接口就是个问题产物:

  open到底open什么?文档,还是HTTP连接,还是两个都打开?也就是说,从概念上来讲,IFileHttpReader就存在矛盾,这样的概念很显然是难以维护的。其次,我们完全没有办法为两个分支提供不同的实现,当然,其根源是IFileHttpReader的错误设计导致的,不采用我们这里提到的技术,问题依然存在。现在引入一个结论:假如某个接口的基类树中多次出现同一个接口,我们的技术无法为这些接口分别提供不同的实现。这里的解决方案是抛弃IFileHttpReader,引入Combine, 我们能够这样解决问题:

typedef Reform<
Combine< ImpFileReader <IFileReader>, ImpHttpReader <IHttpReader> >,
ImpRefCount, ImpReader
>::type ConcreteFileHttpReader;

  假设,ImpReader不能同时满足两个分支的需要,我们能够这么做:

typedef Reform <
Combine< ImpFileReader < ImpReaderA<IFileReader> >,
ImpHttpReader < ImpReaderB <IHttpReader> >
>,
ImpRefCount
>::type ConcreteFileHttpReader;

  利用Combine,我们能够充分发挥多重继承的组合能力,我们既享受了接口设计和实现分离的好处—更容易维护概念了,也充分享有代码复用的能力。并且,将设计决策充分推迟:甚至客户程式员完万能够定制自己的接口实现从而和现有系统结合,这是个完美的Open-Close的设计手段。

<!--[if !supportLists]--><!--[if !supportLists]--><!--[if !supportLists]--><!--[if !supportLists]--> 现在,总结一下在多重继承中的注意事项。

  1.接口尽量是单继承的。

  2.多重继承的接口必须意识到,任何继承树的相同接口只能共享同一份实现。

  3.严苛地去维护接口的概念,不要为了实现问题定义中间的接口(就象那个IFileHttpReader)

  4.合理地利用多重继承的组合能力。<!--[if !supportEmptyParas]-->

  关于最后一条,您能够做一些有趣的探索。给出一个空基类:

struct Over{};

  当然,也能够是其他非模板类。把任何的类都实现成模版形式:ImpClassA<T>, ImpClassB<T>,借助于Combine,我们可能给出这样的定义:

typedef Combine<ImpClassA< Combine<ImpClassB< Over >, ImpClassC< Over > > >,Combine<ImpClassF<Over>, ImpClassB<ImpClassD< Over > >>,ImpClassE<Over>>::type ConcreteSomeClass;

  我们注重于将这些ImpClasses拆成尽可能小的正交模块。那么借助组合技术,可能获得很高的复用性。但是,有句老话,不要为了复用而复用,反正,这里的探索我也是浅尝辄止,出了什么事情和我无关。特别提醒一下,上面代码中Combine里面出现了一个type,您能够尝试在上面施加您喜欢的TMP手法嘛。

  把那些有趣的探索先放在一边。现在,我已把这种技术完整地呈现出来了。然而,没有一项技术是完美的,这里也不例外。这个技术有两个小小的缺陷。

  第一个缺陷则是构造函数的问题。回顾Combine的实现,我们无法为Combine额外提供合适的构造函数。但是,这个问题并不是特别严重,我们完万能够定制自己的Combine。并且,这些不同的Combine能够混合使用。另外,在组装的时候需要小心的维护构造函数的调用链,这可能伤害到复用性。Assignment中也存在类似的问题。运算符重载也可能导致混乱,但是,我一直认为,在值语义之外的类当中重载运算符可是要很谨慎的,很显然,我们这里展示的技术并不适合值语义的类型设计。

  另一个缺陷是工程上的。因为上述的实现都是模板类,所以,我们通常需要将实现在头文档里面提供,这个可能是有些人不愿意的。我将展现一种解决的方法,这就是另一个利器:Pimpl惯用法。
以IReader为例,假设如下接口:

struct IReader : IRefCount
{
virtual bool open(const char* url) = 0;
virtual void read(Buffer& buf, size_t size) = 0;
};

  现在,我们只实现read方法:

class ConcreteImpReader;//前置申明
template<typename Base>
class ImpPartialReader : public Base
{

文章整理:西部数码--专业提供域名注册虚拟主机服务
http://www.west263.com
以上信息与文章正文是不可分割的一部分,如果您要转载本文章,请保留以上信息,谢谢!