【问题标题】:How to extend family of objects (A has many B) with a more specific family of objects (A' <: A has many B' <: B)?如何用更具体的对象家族(A'<:A有很多B'<:B)扩展对象家族(A有很多B)?
【发布时间】: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&lt;AuthoredComment&gt; List&lt;Comment&gt;,因此 AuthoredComment 可能是 Comment 的有效子类型。

现在,我知道 Java 没有泛型类型的协方差,所以不能说 List&lt;AuthoredComment&gt;List&lt;Comment&gt; 的子类型,这很可惜,这可能也是我发现相当复杂的原因设计/实现这个。

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

我是否以完全错误的方式处理这个问题?

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

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

YouTubeComment < AuthoredComment
  + votes

最后,虽然TopicAuthoredTopic 可能最终成为接口或抽象类,但如果对于像YouTubeVideoTopic 这样的具体类,可以向它们添加cmets(它的addComment 方法应该接收YouTubeComment 参数)。

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

方法一:有多种方法返回cmets

基础主题类有一个抽象的getComments: List&lt;Comment&gt;方法,而AuthoredTopic类添加了一个抽象的getAuthoredComments: List&lt;AuthoredComment&gt;方法,最后像YouTubeVideoTopic这样的叶子类有一个具体的getYouTubeComments: List&lt;YouTubeComment&gt;,同时也实现了它们父类的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,但这在基本主题级别并不重要)。

【问题讨论】:

    标签: java oop types


    【解决方案1】:

    只需让getComments 方法返回List&lt;? extends Comment&gt;。像这样:

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

    【讨论】:

    • 感谢“授权”的更好名称!但我不太喜欢这个解决方案,因为Topic 的派生类将有两个不同的列表:一个是基类的 Comments,一个是派生类的 AuthoredComments,尽管前一个列表不会用于任何东西。
    • 耸耸肩您似乎在寻求一种方法来泛化Topic,这就是这样做的方法。 Topic 也可以封装 List&lt;? extends Comment&gt;Topic 的子类必须推断(或假设)? extends Comment 的具体类型,如果它需要访问不是 Comment 固有的功能。
    • 但是AuthoredTopic 知道他们的 cmets 是 AuthoredComments。我正在寻找一种类型安全的解决方案,因此主题子类不需要假设任何关于其 cmets 的类型。
    【解决方案2】:

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

    我同意基于泛型的方法更简洁。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-09-03
      • 1970-01-01
      • 2021-11-28
      • 2014-03-29
      • 2017-04-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多