【发布时间】:2011-12-05 20:12:27
【问题描述】:
很抱歉这个糟糕的标题,但我想不出更好的措辞方式。我将尝试通过一个示例来更清楚地说明这一点(这不是我真正想要解决的问题,但我不想深入讨论特定于问题的细节)。
假设我想为类似论坛的讨论建模。通常,topic 代表要讨论的内容(消息、照片等),cmets 代表人们针对该主题制作的 cmets。所以,在一个非常抽象的层面上,我们有:
Topic
+ title
+ comments: List<Comment>
Comment
+ text
注意:我在这里混合了类似 Java 的语法,因为我正在寻找一种 Java 解决方案,但是任何适用于语义与 Java 相似的语言的解决方案都可能是好的。 p>
假设这个简单的模型对于主题及其 cmets 的一些通用表示已经足够了。但在某些情况下,我希望有更多信息的主题和 cmets。例如,对于一个所有 cmets 都是“作者”的网站(即他们有一个作者......他们不能是匿名的),我想要:
AuthoredTopic < Topic
+ author
+ comments: List<AuthoredComment>
AuthoredComment < Comment
+ author
所以基本上,我想要更专业的主题类型,其中包含更专业的 cmets 类型,但它们仍然可以分别被视为抽象主题和 cmets。以一种非常基本的方式看待它:一个创作的主题有一个作者,但它仍然是一个主题,其创作的 cmets 仍然是 cmets。
现在,在有人读到伪类规范之前,comments 属性是一个只读列表(又名不可变 =D)。如果不是这样,那么设计就会很简单,很容易被破坏,因为可以向 AuthoredTopic 添加任何类型的评论。但如果它是不可变的,那么您可以说 List<AuthoredComment> 是 List<Comment>,因此 AuthoredComment 可能是 Comment 的有效子类型。
现在,我知道 Java 没有泛型类型的协方差,所以不能说 List<AuthoredComment> 是 List<Comment> 的子类型,这很可惜,这可能也是我发现相当复杂的原因设计/实现这个。
那么,我该如何解决这个问题?
我是否以完全错误的方式处理这个问题?
为了让事情变得更糟(或者说更有趣),主题和 cmets 可能会有进一步的子类化。例如:
YouTubeVideoTopic < AuthoredTopic
+ video
+ votes
+ comments: List<YouTubeComment>
YouTubeComment < AuthoredComment
+ votes
最后,虽然Topic 和AuthoredTopic 可能最终成为接口或抽象类,但如果对于像YouTubeVideoTopic 这样的具体类,可以向它们添加cmets(它的addComment 方法应该接收YouTubeComment 参数)。
我现在将解释我尝试解决此问题的两种方法,但它们都没有真正成功或令人信服。
方法一:有多种方法返回cmets
基础主题类有一个抽象的getComments: List<Comment>方法,而AuthoredTopic类添加了一个抽象的getAuthoredComments: List<AuthoredComment>方法,最后像YouTubeVideoTopic这样的叶子类有一个具体的getYouTubeComments: List<YouTubeComment>,同时也实现了它们父类的get*Comments方法.
代码:
abstract class Topic {
private String title;
public Topic(String title) {
this.title = title;
}
public abstract List<Comment> getComments();
// Other getters...
}
class Comment {
private String text;
// Constructor & getters
}
abstract class AuthoredTopic extends Topic {
private String author;
public AuthoredTopic(String title, String author) {
super(title);
this.author = author;
}
public abstract List<AuthoredComment> getAuthoredComments();
@Override
public List<Comment> getComments() {
return Collections.<Comment> unmodifiableList(getAuthoredComments());
}
}
class AuthoredComment extends Comment {
private String author;
}
class YouTubeVideoTopic extends AuthoredTopic {
int votes;
String video; // Yeah.. 'video' is a string for now...
List<YouTubeComment> comments = new ArrayList<YouTubeComment>();
public YouTubeVideoTopic(String title, String author) {
super(title, author);
}
public List<YouTubeComment> getYouTubeComments() {
return Collections.unmodifiableList(comments);
}
@Override
public List<AuthoredComment> getAuthoredComments() {
return Collections.<AuthoredComment> unmodifiableList(comments);
}
public void addComment(YouTubeComment comment) {
comments.add(comment);
}
}
class YouTubeComment extends AuthoredComment {
int votes;
}
它完成了工作,但正如您所见,这是一个非常丑陋的解决方案。不是很干。像 YouTubeVideoTopic 这样的叶子类必须实现很多东西。此外,嵌套的 Topic 子类最终会包含多个 get*Comments,这些在设计中只是杂音。
方法二:添加泛型类型参数
我发现这种方法更容易实现:
class Topic<CommentT extends Comment> {
private String title;
private List<CommentT> comments = new ArrayList<CommentT>();
public Topic(String title) {
this.title = title;
}
public List<CommentT> getComments() {
return Collections.unmodifiableList(comments);
}
public void addComment(CommentT comment) {
comments.add(comment);
}
}
class Comment {
private String text;
// Constructor & getters
}
class AuthoredTopic<CommentT extends AuthoredComment> extends
Topic<CommentT> {
String author;
public AuthoredTopic(String title, String author) {
super(title);
this.author = author;
}
public String getAuthor() {
return author;
}
}
class AuthoredComment extends Comment {
private String author;
}
class YouTubeVideoTopic extends AuthoredTopic<YouTubeComment> {
int votes;
String video; // Yeah.. 'video' is a string for now...
public YouTubeVideoTopic(String title, String author) {
super(title, author);
}
}
class YouTubeComment extends AuthoredComment {
int votes;
}
但是,它的缺点是类型参数“泄漏”到使用这个类的代码中,即使通配符可以使字符开销非常小:
List<Topic<?>> t = getAllTopics();
Topic 不应该有类型参数。一个主题只是有 cmets。没关系,在主题层面,它的cmets是什么类型的,只要是Comments就行;它们的类型是否同质甚至都没有关系(主题的某些子类,例如 YouTubeVideoTopic 可能只有 YouTubeComments,但这在基本主题级别并不重要)。
【问题讨论】: