将类中私有List的迭代器返回是否被认为是不良实践?

6
假设我有两个类:Animal和Zoo,其中Zoo类具有包含Animal实例的私有列表。

我想返回迭代器的原因是为了避免定义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;
    }
}

1
只进行只读操作是可以的。但是,迭代器也有一个remove方法。最好禁用它。 - ZhongYu
5个回答

4

在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( ... 

3

虽然有时返回迭代器是可以接受的,但在这种情况下,iterator()方法会破坏封装性,因为该类提供了一种改变animalList的方法。

结果,获取迭代器的代码,将迭代与调用addAnimal混合在一起,会导致异常。


2

是的,这会破坏封装性。ArrayList 的迭代器具有 remove() 方法。

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

最好提供返回不可修改的列表。
    public List<Animal> animalList() {
        return Collections.unmodifiableList(animalList);
    }

1
我希望返回迭代器的原因是避免定义setter、getter和删除方法。
你完全可以将这些方法添加到你的类中,没有什么不好的理由。你的代码肯定会破坏封装性,但我们稍后再说。现在可以说的是,Zoo违反了一个良好的面向对象设计规则,即“告诉,不要问”。
使用你当前的实现,客户端代码将如下所示:
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();
       }
    }  
}

这样,您将隐藏实现细节,防止客户端代码在使用您的代码时出错,并且您可以处理异常情况,而不是要求客户端代码来处理它们。这就是良好的封装带来的好处。


0
你可以使你的类可迭代:
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);

所以我的回答是,如果你想让你的动物园只读,最好的做法是使它可迭代,可能需要覆盖Iterator


2
如果动物园还包括人类怎么办?当然是作为工作人员,而不是展品。最好将其设计为 for(Animal a : zoo.animals())for(Worker w : zoo.workers()) - ZhongYu
@bayou.io 如果是这样的话,那么在迭代动物园时会迭代父类(Entity?),并且您可以编写方法来获取特定列表的迭代器? - Anubian Noob

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