[英]Container with mixed types vs. single choice principle
I am pondering over my design of a picking mechanism for a 3D tool. 我正在考虑3D工具拾取机制的设计。 In the tool, there are several pickable objects with different capabilities, meaning I have to differentiate between them for the UI (show different palettes etc. based on whatever the user has picked).
在该工具中,有多个具有不同功能的可选取对象,这意味着我必须在UI之间对其进行区分(根据用户所选取的内容显示不同的调色板等)。
The picking mechanism is basically a container that knows all the objects in the document and can answer queries for the objects which are hit by a pick ray. 拾取机制基本上是一个容器,它知道文档中的所有对象,并且可以回答由拾取射线击中的对象的查询。 It returns a list of hits, sorted by their distance to the camera.
它返回一个命中列表,按它们到相机的距离排序。 To register an object with the picker, it must implement the
Pickable
interface: 要向选择器注册对象,它必须实现
Pickable
接口:
class Pickable {
typedef enum {
Entity,
Brush,
Patch
} Type;
virtual Type getType() const = 0;
virtual const BBox3& getBounds() const = 0;
// Returns the distance of the intersection point with the given ray
// or NaN if this object doesn't intersect with the given ray.
virtual double intersects(const Ray3& ray) const = 0;
};
The picker internally stores all objects in a spatial data structure (an octree) which of course only knows them as instances of Pickable
. Pickable
在内部将所有对象存储在一个空间数据结构(八叉树)中,该结构当然仅将它们视为Pickable
实例。 All the objects being hit by the pick ray are then wrapped in instances of Hit
: 然后,所有被拾取线击中的对象都将包裹在
Hit
实例中:
class Hit {
double getDistance() const;
const Vec3& getHitPoint() const;
Pickable* getObject() const;
};
which are then added to a vector and sorted by distance. 然后将其添加到向量并按距离排序。 Now if I want to do something with the hit objects, I have to do something along the lines of
现在,如果我想对命中的对象进行某些操作,则必须按照以下步骤进行操作:
Hit hit = ... // obtain a hit from the picker
Pickable* object = hit.getObject();
switch (object->getType()) {
case Pickable::Entity:
// cast to Entity and perform some operation
break;
case Pickable::Brush:
// cast to Brush and perform some operation
break;
...
}
This of course violates the single choice principle . 这当然违反了单选原则 。 If I ever add a new object type, I have to touch all those switch statements which are strewn all over my codebase.
如果要添加新的对象类型,则必须触摸所有散布在我的代码库中的所有switch语句。 For some operations that I want to apply to these objects, I can use a common super interface such as
对于要应用于这些对象的某些操作,我可以使用通用的超级接口,例如
class Object {
virtual void transform(const Mat4x4& transformation);
// other operations which are applicable to any type of object
// I could even move some UI related functions into this interface:
Palette* getUIPalette() const;
void populateUIPalette(Palette* palette) const;
};
But there are some operations which can only be applied to entities, and some which can only be applied to brushes, and so on. 但是有些操作只能应用于实体,而某些操作只能应用于画笔,依此类推。 The only solution I see is to move all operations into the
Object
interface, providing empty default implementations for operations which do not apply to all object types. 我看到的唯一解决方案是将所有操作移到
Object
接口中,为并非适用于所有对象类型的操作提供空的默认实现。 But this also feels wrong as this would inflate the Object
interface considerably. 但这也感觉不对,因为这会使
Object
接口大大膨胀。
My question is, are these the only two options or am I missing something? 我的问题是,这是仅有的两个选择,还是我错过了什么? I really want to avoid casting and type checking as much as possible, but in this case, I don't see a good way around it.
我真的想尽可能地避免进行类型转换和类型检查,但是在这种情况下,我看不出有什么好的方法。
You may use a Visitor Pattern : 您可以使用访客模式 :
Something like: 就像是:
class IPickableVisitor;
class Pickable {
public:
virtual void accept(IPickableVisitor& t);
};
class Entity : public Pickable {
public:
void accept(IPickableVisitor& t) override { t.visit(*this); }
};
class Brush : public Pickable {
public:
void accept(IPickableVisitor& t) override { t.visit(*this); }
};
class IPickableVisitor
{
public:
virtual void visit(Entity& entity) = 0;
virtual void visit(Brush& brush) = 0;
};
Now you may write: 现在您可以写:
class HitPickableVisitor : public IPickableVisitor
{
public:
virtual void visit(Entity& entity) override
{
// Do the entity hit code.
}
virtual void visit(Brush& brush) override
{
// Do the brush hit code.
}
};
And latter: 后者:
Pickable* object = hit.getObject();
HitPickableVisitor hitPickableVisitor;
object->accept(hitPickableVisitor);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.