【问题标题】:Mutable class as a child of an immutable class可变类作为不可变类的子类
【发布时间】:2010-03-22 16:21:49
【问题描述】:

我想要这样的不可变 Java 对象(高度简化):

class Immutable {

    protected String name;

    public Immutable(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

}

在某些情况下,对象不仅应该是可读的,而且应该是可变的,所以我可以通过继承添加可变性:

public class Mutable extends Immutable {

    public Mutable(String name) {
        super(name);
    }

    public void setName(String name) {
        super.name = name;
    }

}

虽然这在技术上没问题,但我想知道它是否符合 OOP 和 mutable 也是不可变类型的继承。我想避免像 Java 集合 API 那样为不可变对象抛出 UnsupportedOperationException 的 OOP 犯罪。

你怎么看?还有其他想法吗?

【问题讨论】:

  • 这正是Objective C所做的:NSMutableString继承自NSStringNSMutableArray继承自NSArray等等。

标签: java oop inheritance immutability mutable


【解决方案1】:

避免称父类为“不可变”,因为它在子类中成为谎言 - 如果您确实想让一个类不可变,它也应该是最终的,以防止出现此问题。

Joda Time 使用“ReadableXXX”来表示“此类仅提供读取访问权限;其他子类可能是可变的”。我不确定我是否喜欢这个主意,但我不能说我见过很多替代方案。

基本上问题在于表达否定 - Immutable 描述了你不能做什么(改变它)并且不能在子类中明智地强制执行。 (即使Immutable 中的字段是最终字段,它也不会阻止子类拥有自己的可变字段。)

【讨论】:

    【解决方案2】:

    您的子类很糟糕,因为它违反了Liskov substitution principle。不要这样做。

    【讨论】:

    • 不,它没有。 Immutable 的实例可以替换为Mutable 的实例,而不会改变代码的行为。
    • 我不明白为什么这个错误的答案仍然在这里,没有任何编辑。
    • 需要不可变对象的代码需要更新以制作防御性副本。否则在某些情况下可能会发生坏事。也许这不是技术上的 LSP。
    • 答案是正确的。来自维基百科页面,“根据 Meyer 和 America 的定义,MutablePoint 将是 ImmutablePoint 的行为子类型,而 LSP 禁止这样做。”请参阅此以获得更好的解释:softwareengineering.stackexchange.com/questions/238176/…
    【解决方案3】:

    我建议你应该有一个可继承的基“ReadableFoo”类、一个派生的密封 ImmutableFoo 类和其他派生的 MutableFoo 类。不关心 Foo 是否可变的代码可以接受 ReadableFoo。想要保证不会更改的 Foo 的代码可以接受 ImmutableFoo。可能需要更改 Foo 的代码可以接受 MutableFoo。

    请注意,ImmutableFoo 和 MutableFoo 的构造函数通常应该接受 ReadableFoo。这样,任何 Foo 都可以转换为可变或不可变版本。

    【讨论】:

      【解决方案4】:

      不可变类应该是final,以避免可变子类型。

      允许不可变类的子类型打破不可变契约使得首先让该类不可变变得毫无意义。从某种意义上说,Java 允许您这样做可能是合法的(语言中不强制执行不变性),但这样的类不是真正不可变的,只要它可以被子类化。

      这就是为什么 String 是最终的。

      【讨论】:

      • 考虑你需要 ImmutableCollection 基类和 ImmutableList、ImmutableSet 子类。你怎么能让这里的基类最终?问题是没有编译器可以表达模型中可能出现的所有约束。因此,我们应该期望编译器会为我们检查所有内容。
      【解决方案5】:

      我觉得你的代码很好奇。

      为了实现这种不可变行为,我宁愿依赖Immutable 接口,只提供getter 方法,而对象包含两者。这样,依赖于不可变对象的操作会调用接口,而其他操作会调用对象。

      而且,如果您真的不希望将不可变对象转换为可变对象,则可以使用代理和所有企业工具(方面等)。但通常,依靠其他开发者的善意是让他们为自己的错误负责的好方法(例如将不可变的内容转换为可变的)。

      【讨论】:

      • 可以使用代理将对象设置为只读,但它们不会使对象不可变。使可能可变的对象不可变的唯一方法是分离它可能可变的每个部分,在这种情况下,该对象不再是代理。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-04
      • 2010-11-20
      • 2011-03-08
      • 1970-01-01
      • 1970-01-01
      • 2021-09-30
      相关资源
      最近更新 更多