dci::Unknown介绍

来源  DCI in C++ [推荐阅读]

 https://www.jianshu.com/p/bb9c35606d29  

       C++中通过多重继承来实现DCI架构的方式,是一种几近完美的一种方式。如果非要说缺点,只有一个,就是多重继承造成的物理依赖污染问题。由于C++中要求一个类如果继承了另一个类,当前类的文件里必须包含被继承类的头文件。这就导致了领域对象类的声明文件里面事实上包含了所有它继承下来的role的头文件。在context中使用某一个role需用领域对象做cast,所以需要包含领域对象类的头文件。那么当领域对象上的任何一个role的头文件发生了修改,所有包含该领域对象头文件的context都得要重新编译,无关该context是否真的使用了被修改的role。解决该问题的一个方法就是再建立一个抽象层专门来做物理依赖隔离。例如对上例中的Human,可以修改如下:

DEFINE_ROLE(Human)
{
    HAS_ROLE(Worker);
};

struct HumanObject : Human
                   , private Worker
                   , private HumanEnergy
{
private:
    IMPL_ROLE(Worker);
    IMPL_ROLE(Energy);
};

struct HumanFactory
{
    static Human* create()
    {
        return new HumanObject;
    }
};

TEST(...)
{
    Human* human = HumanFactory::create();

    human->ROLE(Worker).produce();

    ASSERT_EQ(1, human->ROLE(Worker).getProduceNum());

    delete human;
}

       为了屏蔽物理依赖,我们把Human变成了一个纯接口类,它里面声明了该领域对象可被context访问的所有public role,由于在这里只用前置声明,所以无需包含任何role的头文件。而对真正继承了所有role的领域对象HumanObject的构造隐藏在工厂里面。Context中持有从工厂中创建返回的Human指针,于是context中只用包含Human的头文件和它实际要使用的role的头文件,这样和它无关的role的修改不会引起该context的重新编译。

       事实上C++语言的RTTI特性同样可以解决上述问题。该方法需要领域对象额外继承一个公共的虚接口类。Context持有这个公共的接口,利用dynamic_cast从公共接口往自己想要使用的role上去尝试cast。这时context只用包含该公共接口以及它仅使用的role的头文件即可。修改后的代码如下:

DEFINE_ROLE(Actor)
{
};

struct HumanObject : Actor
                   , Worker
                   , private HumanEnergy
{
private:
    IMPL_ROLE(Energy);
};

struct HumanFactory
{
    static Actor* create()
    {
        return new HumanObject;
    }
};

TEST(...)
{
    Actor* actor = HumanFactory::create();

    Worker* worker = dynamic_cast<Worker*>(actor);

    ASSERT_TRUE(__notnull__(worker));

    worker->produce();

    ASSERT_EQ(1, worker->getProduceNum());

    delete actor;
}

       上例中我们定义了一个公共类Actor,它没有任何代码,但是至少得有一个虚函数(RTTI要求),使用DEFINE_ROLE定义的类会自动为其增加一个虚析构函数,所以Actor满足要求。最终领域对象继承Actor,而context仅需持有领域对象工厂返回的Actor的指针。Context中通过dynamic_castactor指针转型成领域对象身上其它有效的public role,dynamic_cast会自动识别这种转换是否可以完成,如果在当前Actor的指针对应的对象的继承树上找不到目标类,dynamic_cast会返回空指针。上例中为了简单把所有代码写到了一起。真实场景下,使用ActorWorker的context的实现文件中仅需要包含ActorWorker的头文件即可,不会被HumanObject继承的其它role物理依赖污染。

       通过上例可以看到使用RTTI的解决方法是比较简单的,可是这种简单是有成本的。首先编译器需要在虚表中增加很多类型信息,以便可以完成转换,这会增加目标版本的大小。其次dynamic_cast会随着对象继承关系的复杂变得性能底下。所以C++编译器对于是否开启RTTI有专门的编译选项开关,由程序员自行进行取舍。

       [最后!]DCI框架中提供了一种RTTI的替代工具,它可以模仿完成类似dynamic_cast的功能,但是无需在编译选项中开启RTTI功能。这样当我们想要在代码中小范围使用该特性的时候,就不用承担整个版本都因RTTI带来的性能损耗。利用这种替代技术,可以让程序员精确地在开发效率和运行效率上进行控制和平衡。

UNKNOWN_INTERFACE(Worker, 0x1234)
{
// Original implementation codes of Worker!
};

struct HumanObject : dci::Unknown
                   , Worker
                   , private HumanEnergy
{
    BEGIN_INTERFACE_TABLE()
        __HAS_INTERFACE(Worker)
    END_INTERFACE_TABLE()

private:
    IMPL_ROLE(Energy);
};

struct HumanFactory
{
    static dci::Unknown* create()
    {
        return new HumanObject;
    }
};

TEST(...)
{
    dci::Unknown* unknown = HumanFactory::create();

    Worker* worker = dci::unknown_cast<Worker>(unknown);

    ASSERT_TRUE(__notnull__(worker));

    worker->produce();

    ASSERT_EQ(1, worker->getProduceNum());

    delete unknown;
}

       通过上面的代码,可以看到CUB的dci框架中提供了一个公共的接口类dci::Unknown,该接口需要被领域对象public继承。能够从dci::Unknown被转化到的目标role需要用UNKNOWN_INTERFACE来定义,参数是类名以及一个32位的随机数。这个随机数需要程序员自行提供,保证全局不重复(可以写一个脚本自动产生不重复的随机数,同样可以用脚本自动校验代码中已有的是否存在重复,可以把校验脚本作为版本编译检查的一部分)。领域对象类继承的所有由UNKNOWN_INTERFACE定义的role都需要在BEGIN_INTERFACE_TABLE()END_INTERFACE_TABLE()中由__HAS_INTERFACE显示注册一下(参考上面代码中HumanObject的写法)。最后,context持有领域对象工厂返回的dci::Unknown指针,通过dci::unknown_cast将其转化目标role使用,至此这种机制和dynamic_cast的用法基本一致,在无法完成转化的情况下会返回空指针,所以安全起见需要对返回的指针进行校验。

       上述提供的RTTI替代手段,虽然比直接使用RTTI略显复杂,但是增加的手工编码成本并不大,带来的好处却是明显的。例如对嵌入式开发,这种机制相比RTTI来说对程序员是可控的,可以选择在仅需要该特性的范围内使用,避免无谓的内存和性能消耗。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值