假设存在一个模块。它包含一个管理类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), MutableBar
和ImmutableBar
。请看Apache Commons’Pair的例子。这避免了开发过程中的任何歧义,因为不可变实例根本不会提供任何修改API。
如果我错了,请纠正我,据我所知,如果创建不可变的,我们不提供任何setter或修改状态方法。如果dothis()方法也做同样的事情,我们就不能让它不可变。