使用类型安全建模抽象合成

时间:2012-02-22 14:11:29

标签: java c++ oop inheritance type-safety

我有一个结构问题,我可以使用你的帮助。我将首先解释抽象问题,然后举例说明问题。

考虑一个抽象类 A ,其中包含抽象类 B 的多个实例,还假设 A 中的以下方法: void a.foo1(B val) B a.foo2()。当我们继承类( A'继承 A B'继承 B )并要求 A B 的关系应与 A' B'相同。也就是说,在 A'中: void a.foo1(B'val) B'a.foo2()。第二种方法可行,但不是第一种方法(除非我们进行不安全的类型转换)。换句话说,在 A' a.foo1(B val)应该是非法的,除非参数是 B'的实例。我试图用泛型/模板来模拟这种关系并且很少成功。

制作图表框架时会出现此问题。这里我们有类 Graph GraphVertex 。 (在我的实际实现中,我重载了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; // <--- !!!
}

这里的问题是 removeVertex 方法。假设我们已经使用 AdjacencyMatrixGraph AMGVertex 实现了这些类。在 AdjacencyMatrixGraph 中删除顶点时,我们需要的数据多于抽象基类 GraphVertex 中提供的数据。我们知道参数类型应该 AMGVertex 但是发送另一个类型作为参数不会产生(编译时)错误。

为了解决这个问题,我尝试添加一个新的模板参数,指定实现的类型。那就是:

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>>
{ ... }

但是,这不起作用。由于基类的循环引用,无法使用基类 Graph

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

以上示例在Java中使用泛型具有相同的问题。

那么,无论如何都要以类型安全的方式对关系进行建模吗?还是我坚持使用指针?

感谢您阅读!

编辑:

上述示例用法如下:

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

3 个答案:

答案 0 :(得分:0)

很好的例子(+1)。问题在于您的OO设计,因此不可能使用类型安全的解决方案:

由于A'也属于A类型,因此必须遵循A中描述的contract 。因此,foo1的{​​{1}}必须接受任意A'作为参数,而不仅仅是B

B'foo2的返回类型必须为A'类型。由于B的类型为B',因此B允许covariant返回类型B'(因为n J2SE 5.0。)。

更新:

要解决这个难题,我会进行重新设计,因为foo2实际上不是A'。所以要么

  • A不会继承A'
  • A根本不包含A
  • foo1必须包含A

如果重新设计对您来说太麻烦,那么您将不得不放弃类型安全,例如,如果void a.foo1(B' val)中的foo1A'调用,则抛出非法参数异常1}}不是B。这与java.util.Collection处理可选操作的方式非常相似。

答案 1 :(得分:0)

“当删除AdjacencyMatrixGraph中的顶点时,我们需要的数据多于抽象基类GraphVertex中提供的数据”。 我认为你应该写一个接口,它提供了所需的数据。

如果你有两个在一个点上具有相同行为的子类,并且基类不能在它的类中添加这个行为,因为它不适合每个子类,你可以使用相同的行为来表征具有相同行为的子类接口

答案 2 :(得分:0)

经过一些实验,我得出结论,一般设计不能改为100%类型安全。但是,在某些情况下,您可以帮助确保仅使用正确的子类型调用冒犯的方法。

以图表为例,我们需要做的是确保 GraphVertex 的子类(在这种情况下 AMGVertex )只能访问相关的子的类(用于 AMGVertex ,类 AdjacencyMatrixGraph )。因此确保在 removeVertex(GraphVertex * v)方法中插入正确的类型。为此,非公共可见性可用于抽象类 GraphVertex 访问的方法。此外, GraphVertex 的所有子类型的构造函数的可见性必须是非公开的。当然 AMGVertex 的构造函数必须在 AdjacencyMatrixGraph 中可见,并且 removeVertex 必须在 AMGVertex 中可见。在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.
        { ... }
}

所以, AMGVertex 只能从 AdjacencyMatrixGraph 创建,因此对 AMGVertex :: remove()的调用将始终调用 AdjacencyMatrixGraph :: removeVertex(GraphVertex * v)将正确的子类作为参数。

然而,仍然可以绕过这一点,只需创建一个新的顶点类型,其中 AdjacencyMatrixGraph m_graph 。这是因为从顶点到图形的友谊在抽象基类中。

因此,100%类型安全的解决方案(至少从我收集的方面)是不可能的。

在Java中,我相信使用嵌套类可以实现类似的结果(以克服可见性限制)。

感谢您的回复!如果有人有更好的解决方案,请告诉我们。