65.9K
CodeProject 正在变化。 阅读更多。
Home

动态继承

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.25/5 (15投票s)

2006年10月17日

CPOL

5分钟阅读

viewsIcon

57034

downloadIcon

434

一个设计理念——动态继承

引言

本文介绍了“动态继承”的概念。在动态继承中,您可以在运行时覆盖接口函数的实现。当您有多种接口函数的可能实现,并且需要在不同场景中使用不同组合时,这会非常有用。

我在这里使用了C++代码来解释这个概念。但本文包含了C++和Java的示例代码。概念在两种语言中是相同的。

动机

最近,我遇到了一个奇怪的需求。我需要实现一组接口函数。每个接口函数都有多种可能的实现。只有在运行时,我才能决定对象中每个接口函数的实现。例如,我有一个包含四个函数的接口,比如

virtual void DynamicFunction1() =0;
virtual void DynamicFunction2() =0;
virtual void DynamicFunction3() =0;
virtual void DynamicFunction4() =0;

在不同的场景下,我需要为其中一个或多个接口函数提供不同的实现。如果采用普通的继承,我需要为所有可能的组合提供实现,这使得代码难以维护。代码重复也很多。这个问题促使我思考动态继承。我能否创建一种动态继承,在运行时将所需的函数实现组合在一起?

这就是我得出的方法。

Sample Image - dynami1.gif

在这里,我们创建一个复合对象,其中高优先级函数位于最外层,低优先级函数位于内层。这种实现的优点是我们不需要创建大量类来包含所有可能的函数实现组合。

假设 `DynamicFunction1()` 有 10 种不同的可能实现,`DynamicFunction2()` 有 3 种不同的可能实现,`DynamicFunction3()` 有 4 种不同的可能实现,`DynamicFunction4()` 有一种实现。

如果使用继承,我们需要定义 10x3x4x1 个不同的类来满足所有可能的需要。这使得代码非常复杂且难以维护。

在这些情况下,我们如何使用动态继承?我将通过一个例子来解释。想象一下我们有三个具有不同接口函数实现的类,如下所示:

  • Class1 – 具有 `DynamicFunction1()` 的实现
  • Class2 – 具有 `DynamicFunction1()` 和 `DynamicFunction2()` 的实现
  • Class3 – 具有 `DynamicFunction3()` 的实现

通过动态继承,我们创建一个复合对象,将单独的对象根据它们的优先级包装在一起。对于外部世界,他们可以调用复合对象上的任何接口函数。调用将按如下方式路由。(您可以创建此的不同变体。这里我正在实现动态继承。)任何函数调用,如果该层有实现,则执行该函数并返回。现在,如果没有实现,则调用将路由到内层。如果没有任何层提供实现,则执行核心中的默认实现。

现在让我们仔细看看我们例子中的几个情况。假设对复合对象调用了 `DynamicFunction1()`。由于最外层有一个 `DynamicFunction1()` 函数的实现,它将执行该函数并返回。

Sample Image - dynami3.gif

假设,对复合对象调用了 `DynamicFunction2()`。由于最外层没有 `DynamicFunction2()` 的实现,调用将被路由到下一个内层。

Sample Image - dynami5.gif

示例代码

定义接口

Class CDynamicInterface
{
protected :
    CDynamicInterface * pcChild;
public :
    virtual ~CDynamicInterface(){};
    virtual void DynamicFunction1() =0;
    virtual void DynamicFunction2() =0;
    virtual void DynamicFunction3() =0;
    virtual void DynamicFunction4() =0;
};

核心为所有函数提供默认实现

class CDynamicFunctionCore : public CDynamicInterface  
{
public:
    CDynamicFunctionCore();
    CDynamicFunctionCore(CDynamicInterface * pcChildPtr);

    virtual ~CDynamicFunctionCore();

    virtual void DynamicFunction1();
    virtual void DynamicFunction2();
    virtual void DynamicFunction3();
    virtual void DynamicFunction4();
};

核心构造函数

CDynamicFunctionCore::CDynamicFunctionCore(CDynamicInterface * pcChildPtr)
{
    cout<<"Constructor CDynamicFunctionCore\n";
    pcChild = pcChildPtr;
}

CDynamicFunctionCore::CDynamicFunctionCore()
{
    cout<<"Constructor CDynamicFunctionCore\n";
    pcChild = NULL;
}

为所有核心函数提供默认实现

void CDynamicFunctionCore::DynamicFunction1()
{
    if(pcChild)
        pcChild->DynamicFunction1();
    else
        cout<<"Define DynamicFunction1\n";
}

定义具有不同接口函数定义的新类。请记住,您应该始终从 `CDynamicFunctionCore` 类派生,以便拥有所有函数的默认实现。

class CDynamicFunction1A : public CDynamicFunctionCore  
{
public:
    CDynamicFunction1A(CDynamicInterface * pcChildPtr);
    CDynamicFunction1A(){};
    virtual ~CDynamicFunction1A();
    virtual void DynamicFunction1();
};

复合对象的初始化和操作

CDynamicInterface * MyComposite = new CDynamicFunction1A(new CDynamicFunction1B2B());

MyComposite->DynamicFunction1();
MyComposite->DynamicFunction2();
MyComposite->DynamicFunction3();
MyComposite->DynamicFunction4();

delete MyComposite;

对象销毁 – 使所有析构函数变为虚函数

CDynamicFunctionCore::~CDynamicFunctionCore()
{
    cout<<"Destructor CDynamicFunctionCore\n";
    if(pcChild)
        delete pcChild;
    pcChild = NULL;
}

但有一个问题。假设 `CDynamicFunction1B2B()` 中的 `DynamicFunction2()` 函数调用 `DynamicFunction1()`,那么会执行哪个函数?当然,`CDynamicFunction1B2B` 类型的对象没有任何关于其外层的**信息,它不会调用最外层的 `DynamicFunction1()`。而是会调用 `DynamicFunction1()` 上的它。如果我们想从函数内部调用复合对象的**最外层函数,该怎么办?我们需要保留一个父对象的引用,就像保留子对象的引用一样。我们能否在创建对象的同时做到这一点?如果我们按如下方式初始化,则无法与对象创建一起完成:

    
CDynamicInterface * MyComposite = new CDynamicFunction1A(new CDynamicFunction1B2B());

原因是,当我们调用 `CDynamicFunction1B2B()` 的构造函数时,`MyComposite` 对象的创建尚未完成。这意味着我们无法在构造函数中传递 `MyComposite` 的引用。我们可以做的是,创建一个新函数,该函数将在 `MyComposite` 构建后立即设置父指针。请参阅本文附带的示例代码,了解完整的实现。现在,如果您需要调用**最外层(父)函数,请使用父指针调用该函数。

类图

Sample Image - dynami15.gif

摘要

动态继承可帮助您在运行时覆盖函数。当每个接口函数有许多不同的实现可能时,这非常有用。这避免了创建大量具有接口函数所有可能组合的类定义。这提高了代码的可重用性和可维护性。当我们能够将类定义在外部库(DLL 或 SO)中,并在运行时将对象包装在一起以创建具有新行为的复合对象时,它会更加方便。

历史

  • 2006年10月17日:初始帖子
© . All rights reserved.