根据抽象类快速确定子类

时间:2018-06-22 08:33:26

标签: c++ inheritance design-patterns

我有一个C ++基类CAbstrInstruction和大量直接子类:

class CAbstrInstruction { /* ... */ };

class CSleepInstruction: public CAbstrInstruction { /* ... */ };
class CSetInstruction: public CAbstrInstruction { /* ... */ };
class CIfInstruction: public CAbstrInstruction { /* ... */ };
class CWhileInstruction: public CAbstrInstruction { /* ... */ };
// ...

还有一个CScriptWorker,它公开了一个公共方法execute:

class CScriptWorker
{
    public:
        void execute (const CAbstrInstruction *pI);

    private:
        void doSleep (const CSleepInstruction *pI);
        void doSet (const CSetInstruction *pI);
        void doIf (const CIfInstruction *pI);
        void doWhile (const CWhileInstruction *pI);
        // ...
};

execute方法的当前实现如下:

void CScriptWorker::execute (const CAbstrInstruction *pI)
{
    const CSleepInstruction *pSleep =
        dynamic_cast<const CSleepInstruction *>(pI);

    if (pSleep != NULL)
    {
        doSleep (*pSleep);
        return;
    }

    const CSetInstruction *pSet =
        dynamic_cast<const CSetInstruction *>(pI);

    if (pSet != NULL)
    {
        doSet (*pSet);
        return;
    }

    const CIfInstruction *pIf =
        dynamic_cast<const CIfInstruction *>(pI);

    if (pIf != NULL)
    {
        doIf (*pIf);
        return;
    }

    const CWhileInstruction *pWhile =
        dynamic_cast<const CWhileInstruction *>(pI);

    if (pWhile != NULL)
    {
        doWhile (*pWhile);
        return;
    }

    /* ... */
}

这非常笨拙,需要O(log(n))来调用正确的私有方法。在那儿 有任何设计模式或语言构造可以简化这一过程吗?

说明:我可以将私有执行方法移到指令中 类。 execute方法将变成:

    void execute (const CAbstrInstruction *pI) { pI->execute(); }

但是,那不是我想要的。为什么不? 关注点分离:CAbstrInstruction的实例仅是要执行的操作的描述。它们组成了脚本的抽象语法树。这已经足够了。 CScriptWorker的关注点是实际执行指令中描述的内容。 CScriptWorker知道脚本在其中运行的上下文。CAbstrInstruction不应该知道这一点。

4 个答案:

答案 0 :(得分:2)

<ul> @foreach($users as $user) <li>{{ $user->name }} @if(!$user->children->isEmpty()) @include('user_view', ['users' => $user->children]) @endif </li> @endforeach </ul> 应该定义一个纯虚方法(在您的示例中为CAbstrInstruction),子类应该重写并实现该虚方法。

例如:

execute()

答案 1 :(得分:1)

如果事情很简单,将execute方法的实现转移到CAbstrInstruction的子类中可能是答案。但是,OP明确指出execute方法应放在CScriptWorker中,以分开了解要完成 的问题(指令的工作)从如何完成(CScriptWorker的工作)。这可以通过双重发送(有时也称为访问者模式)实现:

class IInstructionDispatchTarget
{
    public:
    virtual void onDispatch (const CSleepInstruction &instr) = 0;
    virtual void onDispatch (const CSetInstruction &instr) = 0;
};
class CAbstrInstruction
{
    public:
    virtual void dispatch (IInstructionDispatchTarget &t) const = 0;
};
class CSleepInstruction: public CAbstrInstruction
{
     public:
     virtual void dispatch (IInstructionDispatchTarget &t) const override
         { t.onDispatch (*this); }
};
class CSetInstruction: public CAbstrInstruction
{
     public:
     virtual void dispatch (IInstructionDispatchTarget &t) const override
         { t.onDispatch (*this); }
};
class CScriptWorker: public IInstructionDispatchTarget
{
    public:
    void execute (const CAbstrInstruction *pI)
        { pI->dispatch (*this); }
    virtual void onDispatch (const CSleepInstruction &instr) override
    {
        // do sleep
    }
    virtual void onDispatch (const CSetInstruction &instr) override
    {
        // do set
    }
};

execute上调用CScriptWorker时,它将调用指令的dispatch方法。作为回报,该指令使用其特定的onDispatch指针在调度目标上调用this方法,从而调用正确的方法。

接口IInstructionDispatchTarget有两个作用。一方面,它可以确保CAbstrInstruction的实例根本不需要知道CScriptWorker;他们只需要知道接口即可。另一方面,它允许其他分发目标使用相同的机制,例如在遍历说明以优化AST时。

如果认为IInstructionDispatchTarget的存在是不必要的,则可以稍微简化一些事情,如ROX的答案所示。

答案 2 :(得分:1)

当客户端不需要了解对象的具体类型时,最好使用继承。您想使用variant type,因为您有固定数量的已知指令,而执行者需要知道它正在执行哪种指令。使用std::variantboost::variant(如果您是C ++ 17之前的版本)最容易。

#include <variant>

struct Set {};
struct If {};
struct While {};

using Instruction = std::variant<
    Set,
    If,
    While
    >;

#include <iostream>

struct Executor {
    void operator()(Set const&) const { std::cout << "Set\n"; }
    void operator()(If const&) const { std::cout << "If\n"; }
    void operator()(While const&) const { std::cout << "While\n"; }
};

void execute(Instruction const& i) {
    std::visit(Executor(), i);
}

示例:

#include <vector>

int main() {
    for (auto const& i : std::vector<Instruction>{While(), If(), Set()}) {
        execute(i);
    }
}

输出:

While
If
Set

答案 3 :(得分:1)

如果您还有其他将实现IInstructionVisitor接口的类,则访问者模式将很好地工作。这样可以确保所有这些类都可以处理同一组指令类。

如果没有从IInstructorVisitor派生的其他类,则可以对其进行一些简化:-

class CScriptWorker
{
    public:
        void execute (const CAbstrInstruction* pI)
        {
           pI->ResolveInstructionType(*this);
        }

    // Can be made friends of appropriate instruction classes or left public as you see fit
       void doInstruction (const CSleepInstruction* pI);
       void doInstruction (const CSetInstruction* pI);
       void doInstruction (const CIfInstruction* pI);
       void doInstruction (const CWhileInstruction* pI);
    // note the name is now the same, name of the parameter should be enough to tell what's being done
    // also I'd probably make these references not pointers

};


class CAbstrInstruction
{
    public:
    virtual void ResolveInstructionType (CScriptWorker& v) = 0;
};


class CSleepInstruction: public CAbstrInstruction
{
     public:
     void ResolveInstructionType (CScriptWorker& w) override { w.doInstruction (this); }
};

简化的一点好处是,现在的代码略少了,如果添加了新指令,则要修改的代码也略少了,您可以选择访问,访客等以外的名称。