“将不可变接口返回到原始数据。然后您可以更改对象中的字段,但调用者不能除非他通过强制转换作弊。您只公开您希望用户拥有的方法。对类执行相同操作因为子类必须公开其超类所做的一切“
他的意思是你可以作弊,为什么子类很棘手?
答案 0 :(得分:8)
您提供的interface
没有突变方法。然后,您提供只为创建者所知的可变实现。
public interface Person
{
String getName();
}
public class MutablePerson implements Person
{
private String name;
public MutablePerson(String name)
{
this.name = name;
}
@Override
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
}
此时,如果您在任何地方都返回Person
个对象,则某人修改返回对象的唯一方法是作弊并将其强制转换回MutablePerson
。实际上,除非代码是完整的黑客,否则可变对象变为不可变。
Person person = new MutablePerson("picky");
// someone is cheating:
MutablePerson mutableAgain = (MutablePerson)person;
mutableAgain.setName("Phoenix");
// person.getName().equals("Phoenix") == true
当没有与一群年轻的程序员打交道时会注意到真正的实现是可变的,因此他们可以将其转换为改变它,然后你提供了不变性的安全性,并且能够将它组合在一起而不需要无尽的构造函数,或使用Builder(实际上,可变版本是Builder)。避免开发人员滥用可变版本的好方法是将可变版本作为包私有,以便只有包知道它。这个想法的否定之处在于,只有在相同的包中实例化它才有效,这可能是这种情况,但在DAO与多个包定义的实现一起使用的情况下显然可能不是这种情况(例如,MySQL,Oracle,Hibernate,Cassandra等,都返回相同的东西,并希望彼此分开以避免混乱他们的包。)
这里的真正关键是人们不应该从Mutable对象构建,除了实现进一步向下的接口。如果你正在扩展,然后返回一个不可变的子类,那么根据定义,如果它公开了一个可变对象,它就不是不可变的。例如:
public interface MyType<T>
{
T getSomething();
}
public class MyTypeImpl<T> implements MyType<T>
{
private T something;
public MyTypeImpl(T something)
{
this.something = something;
}
@Override
public T getSomething()
{
return something;
}
public void setSomething(T something)
{
this.something = something;
}
}
public interface MyExtendedType<T> extends MyType<T>
{
T getMore();
}
public class MyExtendedTypeImpl<T>
extends MyTypeImpl<T>
implements MyExtendedType<T>
{
private T more;
public MyExtendedTypeImpl(T something, T more)
{
super(something);
this.more = more;
}
@Override
public T getMore()
{
return more;
}
public void setMore(T more)
{
this.more = more;
}
}
老实说,这应该是Java中Collection
的实现方式。 readonly接口可以代替Collections.unmodifiable
实现,因此不会让人意外地使用可变对象的不可变版本。换句话说,你永远不应该隐藏不变性,但你可以隐藏可变性。
然后,他们可以撒上真正无法修改的不可变实例,这将使开发人员保持诚实。同样,我可能希望在某处看到上面接口的不可变版本(名称更好):
public class MyTypeImmutable<T> implements MyType<T>
{
private final T something;
public MyTypeImmutable(T something)
{
this.something = something;
}
@Override
public T getSomething()
{
return something;
}
}
答案 1 :(得分:2)
我认为这个陈述措辞不够好,而且他所涉及的不仅仅是不变性(实际上,这个陈述甚至不是真正的不变性)。
这个想法是,如果您返回数据的接口而不是特定的类,则调用者应该只在接口上执行操作。因此,如果您的接口只有getter方法,那么就没有办法操纵数据(没有向下转换)。
考虑这个层次结构
interface AnInterface {
void aGetter();
}
class MyMutableClass {
void aGetter();
void aSetter(...);
}
即使MyMutableClass
是可变的,通过返回AnInterface
,用户也不知道它实际上是一个可变对象。所以该对象实际上并不可变,但你必须向下转换(或使用反射来访问mutator方法)来了解它。
现在让我们说你有
class MyImmutableSubclass extends MyMutableClass {
void anotherGetter();
}
即使子类是“不可变的”(由于它的父类不是不可变的,它也不是真的),如果从方法返回MyImmutableSubclass
,调用者仍然可以调用aSetter
,因为{{暴露它。
通常,建议使用不可变对象以避免“泄漏”状态。任何真正不可改变的东西都是安全的,不受任何操纵和意外的改变。
答案 2 :(得分:1)
你可以作弊,因为你可以改变返回字段的类型,如果你指定为“某事物”可变的话。如果您在公共接口后面“隐藏”您的类并返回该不可变接口,则用户可以通过将您的接口类型转换为您的类来作弊。
使用子类比较棘手,因为类的任何私有成员都不是由子类继承的,而是受保护的和公共的。这意味着您可以从外部访问您父类中的任何内容,也可以从外部访问子类,因此您无法像使用界面那样轻松地对用户进行模糊处理。我认为这实际上是可行的,因为你可以覆盖父方法,虽然我没有看到它的重点。