在各种游戏引擎的上下文中,请考虑以下基于组件的设计:
鉴于上述情况,Node将有两种方法:
addBehavior
:向Node添加具体的行为类型。
getBehaviors
:从a的节点返回所有行为
指定的类型,包括行为,意思是“所有行为类型”,可选择通过某个谓词进行过滤。
我的困境是是否使用模板与常规继承来实现上述,因为我发现两种方法都存在缺陷;到目前为止,我对这两种方法都不满意,我不知道是否有任何明确的原因可以解释为什么我会将一种实现方式放在另一方面。
考虑以下起点:
class Behavior {
public:
virtual void doYourThing() = 0;
};
class Jump : public Behavior {
public:
virtual void doYourThing() {
// Jump!
}
};
class Run : public Behavior {
public:
virtual void doYourThing() {
// Run at runSpeed!
}
int runSpeed;
};
class Node {
// addBehavior:
// getBehaviors:
private:
unordered_map<type_index, vector<Behavior*>> behaviorsM;
};
现在是实施addBehavior
和getBehavior
继承方法:
void addBehavior(Behavior& behavior) {
behaviorsM[typeid(behavior)].push_back(&behavior);
}
template<class ElementType>
using Predicate = function<bool(ElementType& e, bool& stop)>;
const vector<Behavior*> getBehaviors(ClassType type = typeid(Behavior), Predicate<Behavior> predicate = nullptr) {
vector<Behavior*> unfilteredBehaviors;
if (type == typeid(Behavior)) {
for (auto pair : behaviorsM) {
vector<Behavior*>& behaviors = behaviorsM[pair.first];
unfilteredBehaviors.insert(end(unfilteredBehaviors), begin(behaviors), end(behaviors));
}
}
else {
unfilteredBehaviors = behaviorsM[type];
}
if (!predicate) {
predicate = [](const Behavior& b, bool& stop){return true;};
}
bool stop = false;
vector<Behavior*> results;
for (auto behavior : unfilteredBehaviors) {
if(predicate(*behavior, stop)) {
results.push_back(behavior);
if (stop) {
break;
}
}
}
return results;
}
到目前为止一切顺利。但是考虑一下我是否向Node
添加了一些行为,之后我想设置它可能具有的任何Run
行为的runSpeed(并且我不再拥有自己对行为的引用;否则我只是通过那些打字的参考设置它们。情况将是这样的:
Jump j1;
Run r1;
Run r2;
node.addBehavior(j1);
node.addBehavior(r1);
node.addBehavior(r2);
// Later on... no longer have the refs j1,r1,r2, so I need to query the node:
for (Behavior* behavior : node.getBehaviors(typeid(Run))) {
static_cast<Run*>(behavior)->runSpeed = 20;
}
好处:
坏事:
因此...
“通用”方法:
template <class BehaviorType,
typename enable_if<
is_base_of<Behavior, BehaviorType>::value &&
!is_abstract<BehaviorType>::value,
BehaviorType>::type* = nullptr>
void addBehavior(BehaviorType& behavior) {
behaviorsM[typeid(BehaviorType)].push_back(&behavior);
}
template<class ElementType>
using Predicate = function<bool(ElementType& e, bool& stop)>;
template <class BehaviorType,
typename enable_if<
is_base_of<Behavior, BehaviorType>::value,
BehaviorType>::type* = nullptr>
const vector<BehaviorType*> getBehaviors(Predicate<BehaviorType> predicate = nullptr) {
vector<Behavior*> unfilteredBehaviors;
type_index requestedType = typeid(BehaviorType);
if (requestedType == typeid(Behavior)) {
for (auto pair : behaviorsM) {
vector<Behavior*>& behaviors = behaviorsM[pair.first];
unfilteredBehaviors.insert(end(unfilteredBehaviors), begin(behaviors), end(behaviors));
}
}
else {
unfilteredBehaviors = behaviorsM[requestedType];
}
if (!predicate) {
predicate = [](const BehaviorType& b, bool& stop){return true;};
}
bool stop = false;
vector<BehaviorType*> results;
for (auto behavior : unfilteredBehaviors) {
BehaviorType* concreteBehavior = static_cast<BehaviorType*>(behavior);
if(predicate(*concreteBehavior, stop)) {
results.push_back(concreteBehavior);
if (stop) {
break;
}
}
}
return results;
}
嗯......好吧,让我们看看。现在我可以这样做:
for (Run* behavior : node.getBehaviors<Run>()) {
behavior->runSpeed = 20;
}
如果我觉得它更具描述性,我也可以这样做:
node.addBehavior<Jump>(j1);
node.addBehavior<Run>(r1);
node.addBehavior<Run>(r2);
但我认为这不是免费的。我看待它的方式:
好处:
坏事:
所以最重要的是,让用户做演员表会感觉很糟糕,模板版本为班级的客户提供了更好的用户体验,但代价是一堆东西。所以我不确定这是否适用于模板,如果它有所有缺点。