如果我有一个如下所示的课程:
class A {
private List<B> bs = new ArrayList<B>;
public List<B> getBs() {
return bs;
}
}
它包含B类型的对象列表。
另一个类使用此列表并将列表中的一个Object交给另一个C类对象:
class SomeOtherClass {
private C c;
public void someMethod() {
//code...
ArrayList<B> bs = mA.getBs();
c.setB(bs.get(0));
}
}
最后,对象b的成员字段的某些值会发生变化。
class C {
private B b;
public void setB(B b) {
this.b = b;
this.b.ChangeValue();
}
}
这种方式当然会修改原始列表中的对象,这就是我想要的。
但是,我发现C类修改了类A的列表bs
中的对象的值非常令人困惑。对于另一个开发人员,很难看到此方法更改了值。
所以我认为我在这里做的事情被认为是不好的做法,不是吗?`
编写此类代码的更好方法是什么?
答案 0 :(得分:1)
我同意你的推理。这个方法:
public void setB(B b) {
this.b = b;
this.b.ChangeValue();
}
实施得不是很好。它最好应该重构。如果这不是一个选项,它应该清楚地记录传递给方法时B
参数被修改。如果可能,也应该从方法名称中明确这一点。也许您可以将其命名为setAndUpdate(B b)
。
此方法:
class A {
private List<B> bs = new ArrayList<B>;
public List<B> getBs() {
return bs;
}
}
可能可以接受:
如果A
的接口应该允许插入和删除B
个对象,我会说可以分发支持列表以利用列表操作方法,例如{ {1}},addAll
等由clear
提供。
如果List
确实属于bs
的内部状态,而A
则涉及A
的修改(例如,如果需要维护一些涉及bs
的不变量,那么它应该不泄漏支持数据结构。
您可以在标准集合API中找到这两种变体的示例。例如,bs
和keySet
会公开影响从中检索地图的集合。另一方面,entrySet
会返回一份副本。
如果要公开支持列表的只读版本,可以执行
List.toArray
答案 1 :(得分:1)
我认为我在这里做的事情被认为是不好的做法,不是吗?
是的,通过getter暴露你的类的可变部分可能会产生误导,因为数据封装得不够好。更改数据没有任何问题,只是它以一种不会立即脱颖而出的方式完成。
没有通用的规则来解决这个问题。通常,您应该避免直接在类中返回可变对象。相反,请在适当的位置提供getter和setter,并且不要向调用者提供List<B>
:
class A {
private List<B> bs = new ArrayList<B>;
public void getSizeB() {
return bs.size();
}
public B getB(int i) {
return bs.get(i);
}
public void setB(int i, B b) {
bs.set(i, b);
}
public void addB(B b) {
bs.add(B);
}
... // Add more methods as needed
}
我们的想法是控制进入List<B>
的内容:由于没有直接访问权限,您可以验证进入列表的每个B
。使用您的类A
的代码必须明确说明它所做的所有修改,因为它无法直接获取列表并随意执行任何操作。
答案 2 :(得分:1)
首先,我要说getBs()
是一个坏主意,因为该方法的调用者可以清除列表或向其添加元素,这对{的状态更为严重。 {1}}而不仅仅是更改列表中某个元素的属性。
您可以将其替换为A
,它返回该列表的第i个元素。
现在,如果getB(int)
是不可变的,那么您的工作就完成了,B
的调用者无法更改A状态中的任何内容。
如果B是可变的,您可以决定getB(int)
会返回getB(int)
的副本。
总结一下,我会替换:
bs.get(i)
使用
public void getBs() {
return bs;
}
或
public void getB(int i) {
return bs.get(i);
}
当然,您还应该添加范围检查,以确保public void getB(int i) {
return new B(bs.get(i));
}
是有效的索引。