简体   繁体   English

混合类型容器与单选原则

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM