(在类中)返回私有列表的迭代器是否被认为是错误的做法



假设我有两个类:Animal和Zoo,它们有包含Animal实例的私有List。

我想要返回迭代器的原因是避免定义setter、getter和删除方法。

这会破坏封装吗?

class Zoo{
    private List<Animal> animalList;
    public Zoo(){
        animalList = new ArrayList<Animal>();
    }
    public void addAnimal(Animal animal){
        animalList.add(animal);
    }
    public Iterator<Animal> iterator(){
        return animalList.iterator();
    }
}
class Animal{
    private String name;
    private double weight, height;
    Animal(String name, double weight, double height){
        this.name = name;
        this.weight = weight;
        this.height = height;
    }
}

在Iterable接口之外使用Iterator是极不常见的。我建议不要这样做。

我认为这会更好:

public Iterable<Animal> animals(){
    return Collections.unmodifiableList( animalList );
}
for(Animal a : zoo.animals()) {
    //do something
}

我反对Zoo implements Iterable<Animal>;不要引入不必要的类型关系。

在Java 8中,一种更可取的做法可能是使用Stream而不是Iterable

public Stream<Animal> animals(){
    return animalList.stream();
}
zoo.animals().forEach( ... 

尽管在某些情况下返回迭代器是可以接受的,但在这种特殊情况下,iterator()方法会破坏封装,因为该类提供了一个变异animalList的方法。

因此,获得迭代器并将迭代与对addAnimal的调用混合在一起的代码将导致异常。

是的,它破坏了封装。ArrayList的迭代器具有remove()方法。

    Zoo zoo = new Zoo();
    // .....
    for (Iterator<Animal> i = zoo.iterator(); i.hasNext(); ) {
        i.remove();
    }

最好提供返回不可修改的列表。

    public List<Animal> animalList() {
        return Collections.unmodifiableList(animalList);
    }

我想要返回迭代器的原因是避免定义setter、getter和删除方法。

没有充分的理由不应该将这些方法添加到类中。您的代码肯定会破坏封装,但我们稍后会讨论。目前,可以肯定地说,Zoo打破了一个称为Tell Don't Ask的良好OO设计规则。

对于您当前的实现,客户端代码将如下所示:

zoo.iterator().remove();
zoo.iterator().next().getName();

上面的代码确实不可读。理想的情况是拥有这样的东西:

zoo.removeLastAnimal();
zoo.getNextAnimalName();

Zoo类可以修改如下:

class Zoo{
    private List<Animal> animalList;
    Iterator<Animal> iterator;
    public Zoo(){
        animalList = new ArrayList<Animal>();
        iterator = animalList.iterator();
    }
    public void removeLastAnimal() {
        try {
           iterator.remove();
        } catch(IllegalStateException e) {
            //handle exception
        }
    }      
    public String getNextAnimalName() {
       if(iterator.hasNext()) {
          return iterator.next().getName();
       }
    }  
}

通过这种方式,您将向外部世界隐藏实现细节。您将防止客户端代码在使用代码时出错。您将能够处理异常情况,而不是要求客户端代码来处理它们。这就是好的encapsulation

您可以使类可迭代:

public class Zoo implements Iterable<Animal> {
    ...
    public Iterator<Animal> iterator() {
        return animalList.iterator();
    }
}

然后你可以做这样的事情:

for (Animal a : zoo) {
    ...
}

如果你返回迭代器,你必须这样做:

for (Animal a : zoo.iterator()) {
    ...
}

这有点多余。


您也可以编写自己的迭代器来防止用户调用iterator.remove()并修改您的列表:

public class ReadOnlyAnimalIterator implements Iterator<Animal> {
    private Iterator iter;
    public ReadOnlyIterator(List<Animal> list) {
        this.iter = list.iterator();
    }
    public boolean hasNext() {
        return iter.hasNext();
    }
    public Animal next() {
        return iter.next();
    }
    public void remove() {
        throw new UnsupportedOperationException();
    }
}

然后在iterator()方法中:

return new ReadOnlyAnimalIterator(list);

所以我对这个问题的回答是,最好让你的zoo可迭代,如果你想让它只读,可能会覆盖Iterator

最新更新