为什么允许设置一个集合是一种不好的做法?

4

假设我们有一个包含简单集合(例如列表)的类。该类包含构造函数、getter和setter。我被告知直接设置集合是一种不好的做法。

class Example{
    private String id;
    private List<String> names;

    public Example(String id, List<String> names){
        this.id = id;
        this.names = names;
    }

    public String getId(){
        return id;
    }

    public List<String> getNames(){
        return names;
    }

    public void setId(String id){
        this.id = id;
    }

    public void setNames(List<String> names){
       this.names = names;
    }
}

有人能指出编写方法setNames()的缺点吗?

1
你不应该在getter方法中返回一个集合而没有进行防御性拷贝,原因是这样会破坏封装性。 - shmosel
1
构造函数和 setter 方法存在相同的问题:因为您没有进行防御性拷贝,所以调用者可以更改 Example 持有的列表。 - Andy Turner
你没有封装。假设这个类的实例的目的是维护一个id和一组名称之间的关系。但是任何人都可以在不改变名称的情况下更改id,也可以在不改变id的情况下更改名称。因此,根本没有任何保证,与其使用设置器和获取器,不如直接使用公共变量。 - undefined
6个回答

4
set和get操作背后的逻辑是允许验证或替换内部表示,如果您让外部类设置特定实现,则会失去对插入逻辑(允许重复?有序?可变?)的控制,并使对象更难使用,因为使用者必须决定这些问题,当他们很可能并不关心。

3
很多内置集合都是可变的,因此存储这样的值可能会允许外部类以你没有计划的方式修改你的Example的内部状态。请考虑以下片段:
List<Stirng> names = new ArrayList<>();
names.add("Stack");
names.add("Overflow");

Example example = new Example();
example.setNames(names); 
// example.getNames() returns ["Stack", "Overflow"]

names.add("Exchange");
// example.getNames now returns ["Stack", "Overflow", "Exchange"]!

更安全的方法可能是复制传递列表的内容:

public void setNames(List<String> names){
   this.names = new ArrayList<>(names);
}

2

由于私有变量名称是由你的类所拥有,因此你可以确保在类内部对其内容进行控制。如果你将该变量的引用更改为传递进来的列表,那么你就无法确定你的实例不会被外部更改,因为你的类和传递新列表实例的类都将有一个引用/访问它。getNames()方法也是如此-任何调用该方法的类现在都可以完全访问并从类外部更改列表的内容。


2
那样会给您提供两种更改内容的方法(getNames().add(...)setNames(Arrays.asList(...)))。
这很令人困惑。
您应该选择一种选项,并使另一种选项不可能。

1

对于已有的答案,稍微不同的是,无论您是在设置集合属性还是其他类型,设置器是一个不好的实践 (*)。

引用Effective Java 2nd Ed第15项:“最小化可变性”:

[使类不可变]有许多好处:不可变类比可变类更容易设计、实现和使用。它们更不容易出错,更安全。

Oracle教程中也有关于不可变类的描述。


(*) 这并不是说你永远不应该使用可变类;只是你应该将设计类为不可变作为默认位置,并且仅在实际需要时才使它们可变 - 而这种情况比你想象的要少。引用《Effective Java》中的同一项:

除非有很好的理由使它们可变,否则类应该是不可变的


-2
使用 get 和 set 方法允许您在输入引起问题之前对其进行验证和验证。它还可以用于转换进入和退出系统的信息,以方便程序和用户。
编辑
只是为了明确起见,防御措施是我所说的验证和验证的一部分。

获取和设置方法首先暴露了对象的内部状态,违反了最重要的面向对象原则——信息隐藏/封装。如果这个类是用于数据传输对象,没有业务逻辑,那么这可能是可以接受的。在任何其他情况下,应该避免使用获取和设置方法。 - Timothy Truckle
您可以在方法中进行防御性编程,以传输值而不是对象的引用。OU上有一些很好的材料可供参考。 - user8537453
你可以进行防御性编程。这只是一种美好的愿望。Getter和Setter会破坏信息隐藏/封装,支持特性嫉妒,使代码更难理解、重用和维护。(再次强调:信息隐藏/封装不适用于DTO,因此使用Getter和Setter是可以的。) - Timothy Truckle
那么,您建议如何初始化对象并更新它们包含的值?没有它们,您会如何从对象中获取信息? - user8537453
正如我之前提到的:DTO具有getter/setter。如果您想/需要从包含业务逻辑的业务对象中获取信息,则存在设计问题。 - Timothy Truckle
示例中没有逻辑,它是一个DTO。 - user8537453

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接