简体   繁体   English

使用类型安全性对抽象合成进行建模

[英]Modelling abstract compositions with type-safety

I have a structural problem that I could use your help with. 我有一个结构性问题,可以帮助您。 I'll explain the abstract problem first, then an example to illustrate the problem. 我将先解释抽象问题,然后再举例说明问题。

Consider an abstract class A holding a number of instances of an abstract class B , also assume the following methods in A : void a.foo1(B val) and B a.foo2() . 考虑一个抽象类A保持一定数目的一个抽象类B的实例,还假定在A中的以下方法:void a.foo1(B VAL)B a.foo2()。 The problem arises when we inherit the classes ( A' inherits A and B' inherits B ) and demand that the relation A has to B should be the same as A' has to B' . 当我们继承类( A'继承AB'继承B )并要求关系AB的关系应与A'B'的关系相同时,就会出现问题 That is, in A' : void a.foo1(B' val) and B' a.foo2() . 也就是说,在A'中void a.foo1(B'val)B'a.foo2() The second method will work, but not the first one (unless we do a unsafe type-cast). 第二种方法将起作用,但第一种方法将不起作用(除非我们进行不安全的类型转换)。 In other words, in A' : a.foo1(B val) should be illegal unless parameter is an instance of B' . 换句话说,在A'中a.foo1(B val)应该是非法的,除非parameter是B'的实例。 I've tried to model this relationship with generics/templates with little sucess. 我尝试用很少的成功使用泛型/模板为这种关系建模。

This problem arises when making a graph framework. 在制作图形框架时会出现此问题。 Here we have classes Graph and GraphVertex . 这里我们有GraphGraphVertex类。 (In my actual implementation I overload GraphVertex's delete operator.) In C++: (在我的实际实现中,我重载了GraphVertex的delete运算符。)在C ++中:

template<class T> class Graph; // Forward reference.

template<class T> class GraphVertex
{
    public:
        GraphVertex(Graph<T> &graph) : m_graph(graph){}
        virtual ~GraphVertex() {}

        ... // Abstract methods, using T parameter.

        void remove()
        { m_graph.removeVertex(this); }
    protected:
        Graph<T> &m_graph;
}

template<class T> class Graph
{
    public:
        virtual ~Graph(){}

        ...

        virtual GraphVertex<T> *add(T val) = 0;
        virtual void removeVertex(GraphVertex<T> *vertex) = 0; // <--- !!!
}

The problem here is the removeVertex method. 这里的问题是removeVertex方法。 Lets say we have implemented the classes with AdjacencyMatrixGraph and AMGVertex . 可以说我们已经用AdjacencyMatrixGraphAMGVertex实现了这些类。 When removing a vertex in the AdjacencyMatrixGraph we need more data than is provided in the abstract base class GraphVertex . 当移除AdjacencyMatrixGraph中的顶点时,我们需要的数据要比抽象基类GraphVertex中提供的数据更多。 We know that the parameter type should be AMGVertex but sending another type as parameter would not generate an (compile-time) error. 我们知道参数类型应该AMGVertex,但是发送另一个类型作为参数不会产生(编译时)错误。

To solve this problem I've tried to add a new template parameter, specifying the implemented type. 为了解决这个问题,我尝试添加一个新的模板参数,指定实现的类型。 That is: 那是:

template<class T, class G> class GraphVertex
{
    public:
        GraphVertex(G &graph) : m_graph(graph) {}
        ~GraphVertex() {}

        ...

        void remove() { m_graph.removeVertex(this); }

    protected:
        G &m_graph;
}

template<class T, class V> class Graph
{
    public:
        virtual ~Graph() {}

        ...

        virtual V *add(T val) = 0;
        virtual void removeVertex(V *vertex) = 0;
}

template<class T> AdjacencyMatrixGraph; // Forward declaration.

template<class T> AMGVertex : public GraphVertex<T, AdjacencyMatrixGraph<T>>
{ ... }

template<class T> AdjacencyMatrixGraph : public Graph<T, AMGVertex<T>>
{ ... }

However, this will not work. 但是,这将不起作用。 It is not possible to use the base class Graph due to circular reference of the base classes. 由于基类的循环引用,因此无法使用基类Graph

Graph<int> *p = new AdjacencyMatrixGraph<int>(); // Won't work.

The example above has the same problems in Java with generics. 上面的示例在使用泛型的Java中也存在相同的问题。

So, is there anyway to model the relation in a type-safe way? 因此,有没有以类型安全的方式对关系建模? Or am I stuck with casting pointers around? 还是我坚持在周围投掷指针?

Thank you for reading! 感谢您的阅读!

EDIT: 编辑:

An example usage of the above could look like: 上面的示例用法如下所示:

Graph<int> *someGraph = getSomeGraph();
GraphVertex<int> *newVertex = someGraph->add(3);
...
newVertex->remove();

Nice example (+1). 很好的例子(+1)。 The problem is in your OO design, so a type-safe solution is not possible: 问题出在您的OO设计中,因此不可能使用类型安全的解决方案:

Since A' is also of type A , it must follow the contract described in A . 由于A'的类型也为A ,因此它必须遵循 A 所述的合同 Hence foo1 of A' must accept arbitrary B as parameter, not only B' . 因此, A' foo1必须接受任意的B作为参数,而不仅是B'

The return type of foo2 in A' must be of type B . A'foo2的返回类型必须是B类型。 Since B' is of type B , the covariant return type B' is allowed for foo2 (since n J2SE 5.0.). 由于B'是式的B ,所述协变返回类型B'被允许用于foo2 (由于n J2SE 5.0)。

Update: 更新:

To solve this dilemma, I would do a redesign, since your A' is not really an A . 为了解决这个难题,我将进行重新设计,因为您的A'并不是真正的A So either 所以要么

  • A' does not inherit from A , or A'不继承自A ,或
  • A does not contain foo1 at all, or A根本不包含foo1 ,或者
  • A must contain void a.foo1(B' val) instead. A必须包含void a.foo1(B' val)代替。

If redesigning is too much a trouble for you, you will have to give up type safety, and, for instance, throw an Illegal Argument Exception if foo1 in A' is called with a B that is not a B' . 如果重新设计太多对你的麻烦,你将不得不放弃类型安全,并且,例如,抛出一个非法参数异常,如果foo1A'是带一个B不是一个B' This is quite similar to how java.util.Collection handles optional operations. 这与java.util.Collection处理可选操作的方式非常相似。

"When removing a vertex in the AdjacencyMatrixGraph we need more data than is provided in the abstract base class GraphVertex". “在AdjacencyMatrixGraph中删除顶点时,我们需要的数据比抽象基类GraphVertex中提供的数据更多”。 I think you should write an Interface which offers the data that are needed. 我认为您应该编写一个提供所需数据的接口

If you have two sub-classes with same behavior at one point and the base-class cannot add this bahavior in it's class, because it wont't fit to every subclass you can characterize sub-classes with same bahavior with an extra interface. 如果您同时具有两个行为相同的子类,并且基类无法将此行为添加到其类中,因为它并不适合每个子类,则可以使用额外的接口来表征具有相同行为的子类。

After some experimenting I have concluded that the general design cannot be changed to be 100% type-safe. 经过一些试验,我得出结论,一般设计不能更改为100%类型安全的。 However, there are some situations where you can help ensure that the offended method is only called with the correct sub-type. 但是,在某些情况下,您可以帮助确保仅使用正确的子类型调用有问题的方法。

Taking the graph example, what we need to do is to insure that a sub-class of GraphVertex (in this case AMGVertex ) only can access the related sub-class of Graph (that is for AMGVertex , class AdjacencyMatrixGraph ). 以图为例,我们需要做的是确保GraphVertex的子类(在本例中为AMGVertex )只能访问Graph的相关子类(对于AMGVertex ,类AdjacencyMatrixGraph )。 Hence insuring that the correct type is inserted at the removeVertex(GraphVertex *v) method. 因此,请确保在removeVertex(GraphVertex * v)方法中插入了正确的类型。 To do this, a non-public visibility can be used for methods that are accessed by the abstract class GraphVertex . 为此,可以对抽象类GraphVertex访问的方法使用非公共可见性。 Also the visibility of the constructors of all the sub-types of GraphVertex must be non-public. 同样, GraphVertex的所有子类型的构造函数的可见性也必须是非公开的。 Of course the constructor of AMGVertex must be visible from AdjacencyMatrixGraph and the removeVertex must be visible from AMGVertex . 当然,必须从AdjacencyMatrixGraph中看到AMGVertex的构造函数, 必须从AMGVertex中看到removeVertex In C++ this can be done with friends . 在C ++中,可以与朋友一起完成。

template<class T> class GraphVertex
{
    public:
        virtual ~GraphVertex() {}
        void remove() { m_graph.removeVertex(this); }

    protected:
        GraphVertex(Graph<T> &graph) : m_graph(graph) {}
        Graph<T> &m_graph;
}

template<class T> class Graph
{
    friend class GraphVertex<T>;
    public:
        virtual GraphVertex<T> add(T value) = 0;

    protected: // Or private?
        virtual void removeVertex(GraphVertex<T> *v) = 0;
}

template<class T> class AMGVertex : public GraphVertex<T>
{
    friend class AdjacencyMatrixGraph<T>;

    protected: // Or private?
        AMGVertex(AdjacencyMatrixGraph<T> &graph)
        : GraphVertex<T>(graph) {}
}

template<class T> class AdjacencyMatrixGraph : public Graph<T>
{
    public:
        AMGVertex *add(T value) { ... } // Calls AMGVertex's constructor.

    protected: // Or private?
        void removeVertex(GraphVertex<T> *v) // Only visible from this class and through base class.
        { ... }
}

So, AMGVertex can only be created from AdjacencyMatrixGraph , and hence a call to AMGVertex::remove() will always call AdjacencyMatrixGraph::removeVertex(GraphVertex *v) with the correct sub-class as parameter. 因此,只能从AdjacencyMatrixGraph创建AMGVertex ,因此对AMGVertex :: remove()的调用将始终使用正确的子类作为参数来调用AdjacencyMatrixGraph :: removeVertex(GraphVertex * v)

It is however still possible to circumvent this, simply by creating a new vertex type with a AdjacencyMatrixGraph as m_graph . 但是,仍然可以通过使用AdjacencyMatrixGraph作为m_graph创建一个新的顶点类型来规避此问题 This is because the friendship from vertex to graph is in the abstract base-class. 这是因为从顶点到图形的友谊是抽象的基类。

So a 100% type-safe solution is (at least from what I've gathered) not possible. 因此,不可能实现100%类型安全的解决方案(至少从我收集的数据中)。

In Java I believe similar results can be achieved with nested classes (to overcome visibility limitations). 我相信在Java中,使用嵌套类可以达到类似的结果(克服可见性限制)。

Thank you for the replies! 感谢您的答复! If someone has a better solution, let us know. 如果有人有更好的解决方案,请告诉我们。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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