基于抽象类快速确定子类 我有C++基类C抽象指令和大量直接子类: class CAbstrInstruction { /* ... */ }; class CSleepInstruction: public CAbstrInstruction { /* ... */ }; class CSetInstruction: public CAbstrInstruction { /* ... */ }; class CIfInstruction: public CAbstrInstruction { /* ... */ }; class CWhileInstruction: public CAbstrInstruction { /* ... */ }; // ...
还有一个CScriptWorker,它公开了一个公共方法execute:基于抽象类快速确定子类 我有C++基类C抽象指令和大量直接子类: class CAbstrInstruction { /* ... */ }; class CSleepInstruction: public CAbstrInstruction { /* ... */ }; class CSetInstruction: public CAbstrInstruction { /* ... */ }; class CIfInstruction: public CAbstrInstruction { /* ... */ }; class CWhileInstruction: public CAbstrInstruction { /* ... */ }; // ...,c++,inheritance,design-patterns,C++,Inheritance,Design Patterns,还有一个CScriptWorker,它公开了一个公共方法execute: class CScriptWorker { public: void execute (const CAbstrInstruction *pI); private: void doSleep (const CSleepInstruction *pI); void doSet (const CSetInstruction *pI); void
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;
}
/* ... */
}
然而,这不是我想要的。为什么不呢关注点分离:CABSTRI指令的实例只是对所做工作的描述。它们构成了脚本的抽象语法树。这已经足够令人担忧了。CScriptWorker关心的是实际执行指令中描述的操作。CScriptWorker知道脚本运行的上下文。CAbstrInstruction不应该知道这一点。
CAbstrInstruction
应该定义一个纯虚拟方法(execute()
,在您的示例中),您的子类应该覆盖并实现该方法
例如:
class CAbstrInstruction
{
/* ... */
virtual void execute() const = 0;
}
class CSleepInstruction
{
/* ... */
void execute() override const
{
/* your code here */
}
}
/* ... */
void CScriptWorker::execute (const CAbstrInstruction *pI)
{
pI->execute();
}
CAbstrInstruction
应该定义一个纯虚拟方法(execute()
,在您的示例中),您的子类应该覆盖并实现该方法
例如:
class CAbstrInstruction
{
/* ... */
virtual void execute() const = 0;
}
class CSleepInstruction
{
/* ... */
void execute() override const
{
/* your code here */
}
}
/* ... */
void CScriptWorker::execute (const CAbstrInstruction *pI)
{
pI->execute();
}
如果事情简单的话,将
execute
方法的实现转移到CAbstrInstruction
的子类中可能是答案。但是,OP明确规定,执行方法应在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
}
};
在CScriptWorker
上调用execute
时,它调用指令的dispatch
方法。作为回报,指令使用其特定的this
指针调用调度目标上的onDispatch
方法,从而调用正确的方法
接口IInstructionDispatchTarget
有两个用途。一方面,它确保CAbstrInstruction
的实例根本不需要知道CScriptWorker
;他们只需要知道接口。另一方面,它允许其他调度目标使用相同的机制,例如在遍历指令以优化AST时
如果认为存在
IInstructionDispatchTarget
是不必要的,那么事情可以稍微简化,如ROX的回答所示 如果事情简单的话,将execute
方法的实现移动到CAbstrInstruction
的子类中可能是一个答案。但是,OP明确规定,执行方法应在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
}
};
在CScriptWorker
上调用execute
时,它调用指令的dispatch
方法。作为回报,指令使用其特定的this
指针调用调度目标上的onDispatch
方法,从而调用正确的方法
接口IInstructionDispatchTarget
有两个用途。一方面,它确保CAbstrInstruction
的实例根本不需要知道CScriptWorker
;他们只需要知道接口。另一方面,它允许其他调度目标使用相同的机制,例如在遍历指令以优化AST时
如果认为存在
IInstructionDispatchTarget
是不必要的,那么事情可以稍微简化,如ROX的回答所示 当客户端不需要知道对象的具体类型时,最好使用继承。您希望使用,因为您有固定数量的已知指令,并且您的执行者需要知道它正在执行哪种指令。这是最容易使用的,或者如果您是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);
}
当客户端不需要知道对象的具体类型时,最好使用继承。您希望使用,因为您有固定数量的已知指令,并且您的执行者需要知道它正在执行哪种指令。这是最容易使用的,或者如果您是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);
}
如果有其他类将实现IInsttructionVisitor接口,那么visitor模式工作得很好。这确保了所有这些类都可以处理同一组指令类 如果没有从IInstuctorVisitor派生的其他类,则可以稍微简化它:-
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); }
};
简化的一点好处是,现在代码稍微少一些,要修改的代码稍微少一些。如果添加了新指令,则可以选择除visit、visitor等以外的名称。如果有其他类将实现IInstructionVisitor接口,则visitor模式工作得很好。这确保了所有这些类都可以处理同一组指令类 如果没有从IInstuctorVisitor派生的其他类,则可以稍微简化它:-
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); }
};
简化的一点好处是,现在代码稍微少了一点,要修改的代码稍微少了一点。如果添加了新指令,您可以选择除visit、visitor等以外的名称。查看visitor设计模式您是对的,我认为这可能会解决问题。您的
CAbstrInstruction
没有提供任何内容,你所做的相当于铸造void*
。作为接口,这些说明应该提供什么?不确定你的意思。当然,我已经删除了类中的所有细节。我的意思是,您没有使用任何有意义的特性