[英]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'继承
A ,
B'继承
B )并要求关系
A与
B的关系应与
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 .
这里我们有Graph和
GraphVertex类。 (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 .
可以说我们已经用AdjacencyMatrixGraph和
AMGVertex实现了这些类。 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)。
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'
. 如果重新设计太多对你的麻烦,你将不得不放弃类型安全,并且,例如,抛出一个非法参数异常,如果
foo1
在A'
是带一个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.