我有一个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不应该知道这一点。
答案 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::variant
或boost::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); }
};
简化的一点好处是,现在的代码略少了,如果添加了新指令,则要修改的代码也略少了,您可以选择访问,访客等以外的名称。