我有一个结构问题,我可以使用你的帮助。我将首先解释抽象问题,然后举例说明问题。
考虑一个抽象类 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();
答案 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)
中的foo1
被A'
调用,则抛出非法参数异常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中,我相信使用嵌套类可以实现类似的结果(以克服可见性限制)。
感谢您的回复!如果有人有更好的解决方案,请告诉我们。