3

“返回原始数据的不可变接口。然后您可以更改对象中的字段,但调用者不能,除非他通过强制转换作弊。您只公开您希望用户拥有的方法。对类做同样的事情比较棘手,因为子类必须公开其超类所做的一切”

他是什么意思你可以作弊,为什么子类很棘手?

来源: http: //mindprod.com/jgloss/immutable.html

4

3 回答 3

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

老实说Collection,Java 中的 s 应该是这样实现的。只读接口可以代替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;
    }
}
于 2012-10-18T01:36:16.437 回答
2

我认为该声明措辞不好,他所触及的不仅仅是不变性(事实上,该声明甚至不是真正的不变性)。

这个想法是,如果您返回数据的接口,而不是特定的类,调用者应该只在接口上执行操作。因此,如果您的接口只有 getter 方法,那么应该没有办法操作数据(没有向下转换)。

考虑这个层次结构

interface AnInterface {
  void aGetter();
}

class MyMutableClass {
   void aGetter();
   void aSetter(...);
}

即使MyMutableClass是可变的,通过返回AnInterface,用户并不知道它实际上是一个可变对象。所以该对象实际上并不是可变的,但您必须向下转换(或使用反射来访问 mutator 方法)才能知道这一点。

现在假设你有

class MyImmutableSubclass extends MyMutableClass {
   void anotherGetter();
}

即使子类是“不可变的”(它不是真的,因为它的父类不是不可变的),如果你MyImmutableSubclass从方法返回,调用者仍然可以调用aSetter,因为MyMutableClass它暴露了它。

通常,建议使用不可变对象以避免“泄漏”状态。任何真正不可变的东西都不会受到任何操纵和意外更改的影响。

于 2012-10-18T01:38:40.020 回答
1

您可以作弊,因为如果您将类型转换为可变的“某物”,您可以更改返回字段的类型。如果您将您的类“隐藏”在公共接口后面并返回该不可变接口,则用户可以通过将您的接口类型转换为您的类来作弊。

使用子类比较棘手,因为类的任何私有成员都不是由子类继承的,而是受保护的和公共的。这意味着您可以从外部在父类中访问的任何内容也可以从外部在子类中访问,因此您不能像使用界面那样容易地混淆用户。我认为这实际上是可能的,因为您可以覆盖父方法,尽管我认为它没有多大意义。

于 2012-10-18T01:44:55.040 回答