集合.unmodiablelist与自定义对象,防止状态从外部更改



假设存在一个模块。它包含一个管理类Foo,其中包含类Bar的实例列表。

public class Foo {
    private List<Bar> bars;
    public doStuff() { ... }
    private doOtherStuff() { ... }
    // method to get bars
    public List<Bar> getBars() { ... }
}

然后你有一个类Bar与公共getter读取Bar的状态。该类还包含改变其状态的mutator。这些变异体可以是公共的,也可以是包级的。public mutator的原因是这个模块类包含了另一个模块(其他包)中其他类使用的状态。因此,Bar的每个实例都是可变的。

public class Bar {
    private int myInt; // simple example, but there are more attributes
    // constructor kept on package level
    Bar(...) { ... }
    // getters on public access 
    public int getMyInt() { ... }
    // change state
    public void doThis(int i) { ... }
    // update state
    void doThat(int j) { ... }
}

我有一个问题,当你想显示Bar(它有多个属性!)的所有实例的概述,外部用户(UI)可能不会修改这个对象的状态。这是可能的,因为有一些public mutator是其他模块所需要的。

搜索后,我似乎可以使用unmodifiableList(List<? extends T> list)提供一个只读列表。像

public List<Bar> getBars() {
    return Collections.unmodifiableList(bars);
}

但是这种方法不起作用,因为这会确保列表本身是只读的。这意味着你不能向它添加元素。但是,一旦您可以检索对象,您仍然可以修改它。

List<Bar> myBars = foo.getBars();
myBars.get(0).doThis(...);

方法doThis()是公共可访问的,所以用户可以在不允许的情况下改变状态。

问题是

(抱歉介绍太长)有没有一个好的方法来解决这个安全问题?当然,我可以使doThis方法非公开,但随后我必须在Foo中添加方法,以便Bar可以从其他模块修改。在这种情况下,我必须在所有模块中的所有公共访问方法(有很多…)做这件事

我想把unmodifiableList方法和Bar的clone()方法结合起来,就像下一个

public List<Bar> getBars() {
    List<Bar> returnList = new ArrayList<Bar>(bars.size());
    for (Bar b : bars) {
        returnList.add(b.clone()); // clone an instance of bar
    }
    return Collections.unmodifiableList(returnList);
}

这应该可以工作,但这是一个好方法吗?特别是当Foo类中的列表bars可能是一个巨大的列表时,当试图显示概述(克隆每个实例)时,这可能会减慢运行时的速度。

Bar实现一个只提供getter的不可变接口,例如ImmutableBar。将Bar的实例作为ImmutableBar s传递给GUI。

澄清我上面关于"不可变包装器"的评论:这个想法是,有一个ImmutableBar实现的Bar接口,它拦截修改,如:

/** Wraps a Bar, but prevents all modification operations. */
public final class ImmutableBar implements Bar {
    private final Bar wrapped;
    public ImmutableBar(Bar bar) {
        wrapped = bar;
    }
    @Override
    public int getMyInt() {
        return wrapped.GetMyInt();
    }
    @Override
    public void doThis(int i) {
        throw new UnsupportedOperationException("Modifications are not allowed");
    }
    // etc.
}

警告:在编译时不禁止修改,但在运行时禁止。但是,您将模仿与您已经使用的Collections#unmodifiableList相同的行为(在尝试修改集合时也会抛出UnsupportedOperationException)。所以这对你来说可能是一个可行的解决方案。

还请记住,如果您获得了包装的Bar,对这些实例的修改仍然会反映在包装器上。如果您设计了一个API,可以在其中控制要公开的实例,那么如果您注意的话,这应该不是一个问题。

建议:我仍然更喜欢@muued建议的解决方案,它只是在一个通用接口Bar内提供"读取"操作,并且具有不可变的实现(仅实现Bar的getter)和可变的实现(另外提供mutator), MutableBarImmutableBar。请看Apache Commons’Pair的例子。这避免了开发过程中的任何歧义,因为不可变实例根本不会提供任何修改API。

如果我错了,请纠正我,据我所知,如果创建不可变的,我们不提供任何setter或修改状态方法。如果dothis()方法也做同样的事情,我们就不能让它不可变。

最新更新