可变类作为不可变类的子类
我想要像这样的不可变 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 和继承该可变的也是不可变的类型。我想避免 OOP 犯罪,为不可变对象抛出 UnsupportedOperationException
,就像 Java 集合 API 所做的那样。
你怎么认为?还有其他想法吗?
I want to have immutable Java objects like this (strongly simplified):
class Immutable {
protected String name;
public Immutable(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
In some cases the object should not only be readable but mutable, so I could add mutability through inheritance:
public class Mutable extends Immutable {
public Mutable(String name) {
super(name);
}
public void setName(String name) {
super.name = name;
}
}
While this is technically fine, I wonder if it conforms with OOP and inheritance that mutable is also of type immutable. I want to avoid the OOP crime to throw UnsupportedOperationException
for immutable object, like the Java collections API does.
What do you think? Any other ideas?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
避免将父类称为“不可变”,因为它会成为子类中的谎言 - 如果您确实想让一个类不可变,那么它也应该是最终的,以防止出现此问题。
Joda Time 使用“ReadableXXX”来表达“此类仅提供读访问;其他子类可能是可变的”的想法。我不确定我是否喜欢这个想法,但我不能说我已经看到了很多替代方案。
基本上,问题在于表达否定 -
Immutable
描述了你不能做什么(改变它)并且不能在子类中明智地强制执行。 (即使Immutable
中的字段是最终的,它也不会阻止子类拥有自己的可变字段。)Avoid calling the parent "Immutable" because it becomes a lie in the child class - if you do want to make a class immutable, it should be final too in order to prevent exactly this problem.
Joda Time uses "ReadableXXX" to give the idea that "this class only provides read access; other subclasses may be mutable". I'm not sure whether I like that idea, but I can't say I've seen many alternatives.
Basically the problem is with expressing a negative -
Immutable
describes what you can't do (mutate it) and that can't be sensibly enforced in subclasses. (Even if the fields withinImmutable
were final, it wouldn't stop a subclass having its own mutable fields.)您的子类很糟糕,因为它违反了 里氏替换原则。不要这样做。
Your subclass is bad because it violates the Liskov substitution principle. Don't do it.
我建议您应该有一个可继承的基“ReadableFoo”类、一个派生的密封 ImmutableFoo 类和其他派生的 MutableFoo 类。不关心 Foo 是否可变的代码可以接受 ReadableFoo。想要保证 Foo 不改变的代码可以接受 ImmutableFoo。需要更改 Foo 的代码可以接受 MutableFoo。
请注意,ImmutableFoo 和 MutableFoo 的构造函数通常应该接受 ReadableFoo。这样,任何 Foo 都可以转换为可变或不可变版本。
I would suggest that you should have an inheritable base "ReadableFoo" class, a derived sealed ImmutableFoo class, and other derived MutableFoo classes. Code which doesn't care whether a Foo is mutable or not can accept a ReadableFoo. Code that wants a Foo that is guaranteed not to change can accept an ImmutableFoo. Code which can need to change a Foo can accept a MutableFoo.
Note that the constructors for both ImmutableFoo and MutableFoo should typically accept a ReadableFoo. That way, any Foo will be convertible to a mutable or immutable version.
不可变类应该是最终的,以避免可变子类型。
允许不可变类的子类型破坏不可变契约使得该类从一开始就变得不可变变得毫无意义。从 Java 允许您这样做的意义上来说,这可能是合法的(语言中不强制执行不变性),但只要可以子类化,这样的类就不是真正不可变的。
这就是为什么 String 是最终的。
Immutable classes should be
final
precisely to avoid mutable sub-types.Allowing a sub-type of an immutable class to break the immutable contract makes it rather pointless to have the class be immutable in the first place. It may be legal in the sense that Java allows you to do it (immutability is not enforced in the language) but such a class isn't truly immutable as long as it can be sub-classed.
This is why String is final.
我发现你的代码很好奇。
为了实现这样的 Immutable 行为,我宁愿依赖 Immutable 接口,只提供 getter 方法,而对象同时包含这两种方法。这样,依赖于不可变对象的操作将调用接口,而其他操作将调用对象。
而且,如果您确实不希望将不可变对象转换为可变对象,则可以使用代理和所有企业工具(方面等)。但通常情况下,依靠其他开发人员的善意是让他们对自己的错误负责的好方法(例如将不可变的内容转换为可变的内容)。
I find your code rather curious.
To implement such an Immutable behaviour, I would rather has relied upon an
Immutable
interface, providing only the getter method, whiile the object contains both. This way, operations relying on the immutable objects would have called the interface, while others would have called the object.And, if you really don't want your immutable objects to be casted as mutable ones, you can then use proxies, and all the enterprise tools (aspects, and so on). But usually, relying upon other developpers' goodwill is a sweet way to make them responsible of their mistakes (like casting the immutable in mutable).