如何使用更具体的对象族扩展对象族(A有很多B)(A'<:A有很多B'<:B)?

时间:2011-10-12 05:21:48

标签: java oop types

对于这个残暴的头衔感到抱歉,但我想不出更好的措辞。我将尝试用一个例子来更清楚地说明这一点(这不是我实际上要解决的问题,但我不想讨论特定问题的细节)。

假设我想模仿论坛式的讨论。通常,主题代表要讨论的内容(消息,照片等),评论代表人们对该主题的评论。所以,在非常抽象的层面上,我们有:

Topic
  + title
  + comments: List<Comment>

Comment
  + text

注意:我在这里混合使用类似Java的语法,因为我正在寻找Java解决方案,但任何适用于语义与Java语言相似的语言的解决方案可能都会很好。

这个简单的模型足以用于某个主题及其注释的一般用途表示。但在某些情况下,我希望有更多信息的主题和评论。例如,对于所有评论都是“创作”的网站(即他们有作者......他们不能匿名),我想要:

AuthoredTopic < Topic
  + author
  + comments: List<AuthoredComment>

AuthoredComment < Comment
  + author

基本上,我希望有更多专业类型的主题,其中包含更多特殊类型的注释,但它们仍然可以被视为抽象主题和评论。以一种非常基本的方式来看待它:一个创作的主题有一个作者,但它仍然是一个主题,其创作的评论仍然是评论。

现在,在任何人查看伪类规范的癫痫发作之前,comments attribure是只读列表(又名不可变= D)。如果不是,那么设计将是简单的并且简单地破坏,因为可以向AuthoredTopic添加任何类型的评论。但如果它是不可变的,那么你可以说List<AuthoredComment> List<Comment>,因此AuthoredComment可能是Comment的有效子类型。

现在,我知道Java没有泛型类型的协方差,所以不能说List<AuthoredComment>是一个子类型List<Comment>,这是一个耻辱,也可能是我'的原因我发现很难设计/实现这个。

那么,我该如何解决这个问题?

我是从一个完全错误的方式接近这个吗?

为了使事情变得更糟(或者说更有趣),可能会进一步对主题和评论进行子类化。例如:

YouTubeVideoTopic < AuthoredTopic
  + video
  + votes
  + comments: List<YouTubeComment>

YouTubeComment < AuthoredComment
  + votes

最后,虽然TopicAuthoredTopic可能最终成为接口或抽象类,但如果像YouTubeVideoTopic这样的具体类可以为它们添加注释(它的addComment方法应该收到YouTube评论论点。)

我现在将解释我为解决这个问题而尝试的两种方法,它们都没有真正成功或令人信服。

方法1:有多种方法可以返回评论

基本主题类有一个抽象的getComments: List<Comment>方法,而AuthoredTopic类添加了一个抽象的getAuthoredComments: List<AuthoredComment>方法,最后像YouTubeVideoTopic这样的叶子类具有一个具体的getYouTubeComments: List<YouTubeComment>,同时也实现了它们父母的*评论方法。

代码:

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,它们只是设计中的噪声。

方法2:添加泛型类型参数

我发现这种方法更容易实现:

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;
}

但是,它的缺点是类型参数“泄漏”到使用此clases的代码中,即使通配符可以使字符开销非常小:

List<Topic<?>> t = getAllTopics();

Topic不应该有类型参数。一个主题只是有评论。在主题层面,只要它们是评论,它的评论类型就没有关系;它们的类型是否是同质的甚至无关紧要(Topic的某些子类,如YouTubeVideoTopic可能只有YouTubeComments,但在基本主题级别无关紧要。)

2 个答案:

答案 0 :(得分:2)

这称为并行层次结构。 c2 wiki有一个很好的讨论。谷歌搜索也带来了好处。

我同意基于通用的方法更清晰。

答案 1 :(得分:0)

getComments方法返回List<? extends Comment>。像这样:

public class Topic {
    public List<? extends Comment> getComments() {
        return new ArrayList<Comment>();
    }
}

public class Comment {

}

public class AuthoredTopic extends Topic {

    @Override
    public List<? extends Comment> getComments() {
        return new ArrayList<AuthoredComment>();
    }
}

public class AuthoredComment extends Comment {

}