程序员必知!迭代器模式的实战应用与案例分析

迭代器模式提供了遍历聚合对象中各元素的方法,通过它无需了解其底层结构,例如,在电商平台商品列表中统计销售额时,可通过迭代器简化遍历过程,加总每个商品销售额,此模式使代码更简洁、易维护,且适用于不同类型的聚合对象,实现轻松扩展。

定义

迭代器模式提供了一种遍历一个聚合对象中各个元素的方法,而不需要知道该对象的底层表示方式,它的主要目的是提供一个统一的、简洁的方式来遍历一个聚合对象的所有元素,而不需要关心该聚合对象的内部表示方式。

举一个业务中形象的例子来说明迭代器模式:假设有一个电商平台的商品列表,这个商品列表由多个商品组成,现在想要统计所有商品的销售额,可以使用迭代器模式来遍历所有商品并计算总销售额,具体来说,可以定义一个商品列表的聚合对象,然后定义一个迭代器,通过迭代器来遍历所有的商品对象,并对每个商品对象执行加总操作,最终得到所有商品的销售额,使用迭代器模式可以使得代码更加简洁、易于维护,并且可以轻松地扩展到其他类型的聚合对象。

代码案例

反例

下面是一个未使用迭代器模式的反例代码,在未使用迭代器模式的情况下,要遍历一个聚合对象(比如一个列表或集合)并对其进行操作,通常可能会直接在client代码中处理聚合对象的内部表示,这样做会导致client代码与聚合对象的内部结构紧密耦合,违反了设计模式的封装原则,如下代码:

java 复制代码
import java.util.ArrayList;  
import java.util.List;  
  
// 产品类  
class Product {  
    private String name;  
    private double price;  
  
    public Product(String name, double price) {  
        this.name = name;  
        this.price = price;  
    }  
  
    public String getName() {  
        return name;  
    }  
  
    public double getPrice() {  
        return price;  
    }  
  
    @Override  
    public String toString() {  
        return "Product{" +  
                "name='" + name + '\'' +  
                ", price=" + price +  
                '}';  
    }  
}  
  
// 产品目录类,未使用迭代器模式  
class ProductCatalog {  
    // 内部使用ArrayList存储产品  
    private List<Product> products = new ArrayList<>();  
  
    // 直接暴露内部列表,违反封装原则  
    public List<Product> getProducts() {  
        return products;  
    }  
  
    public void addProduct(Product product) {  
        products.add(product);  
    }  
}  
  
// 客户端代码  
public class Client {  
    public static void main(String[] args) {  
        ProductCatalog catalog = new ProductCatalog();  
        catalog.addProduct(new Product("Laptop", 999.99));  
        catalog.addProduct(new Product("Smartphone", 499.99));  
        catalog.addProduct(new Product("Tablet", 299.99));  
  
        // 客户端直接操作ProductCatalog的内部结构  
        for (Product product : catalog.getProducts()) {  
            System.out.println(product);  
        }  
  
        // 假设我们需要修改ProductCatalog的内部实现,比如使用LinkedList替代ArrayList  
        // 那么客户端代码也需要相应地进行修改,这违反了开闭原则  
    }  
}	

运行上面的代码会得到以下输出:

ini 复制代码
Product{name='Laptop', price=999.99}  
Product{name='Smartphone', price=499.99}  
Product{name='Tablet', price=299.99}

在这个例子中,ProductCatalog类直接暴露了它的内部列表products,client代码通过getProducts方法直接访问这个列表并进行遍历,这样做的问题是,如果ProductCatalog的内部实现发生变化(比如从ArrayList换成LinkedList),client代码也需要相应地进行修改,因为client代码已经与ProductCatalog的内部实现紧密耦合在一起了。

为了避免这种紧密耦合,应该使用迭代器模式来提供一个统一的遍历接口,从而隐藏聚合对象的内部表示,这样,即使聚合对象的内部实现发生变化,client代码也不需要修改,因为它只依赖于迭代器接口。

正例

下面是一个使用迭代器模式的正例代码,创建一个迭代器接口来定义遍历元素所需的方法,然后为聚合对象创建一个类来实现这个接口,这样做的好处是client代码可以只依赖于迭代器接口,而不是聚合对象的具体实现,从而实现了松耦合,如下代码:

java 复制代码
import java.util.ArrayList;  
import java.util.Iterator;  
import java.util.List;  
  
// 产品类  
class Product {  
    private String name;  
    private double price;  
  
    public Product(String name, double price) {  
        this.name = name;  
        this.price = price;  
    }  
  
    public String getName() {  
        return name;  
    }  
  
    public double getPrice() {  
        return price;  
    }  
  
    @Override  
    public String toString() {  
        return "Product{" +  
                "name='" + name + '\'' +  
                ", price=" + price +  
                '}';  
    }  
}  
  
// 迭代器接口  
interface Iterator<T> {  
    boolean hasNext();  
    T next();  
}  
  
// 产品目录类,使用了迭代器模式  
class ProductCatalog implements Iterable<Product> {  
    // 内部使用ArrayList存储产品  
    private List<Product> products = new ArrayList<>();  
  
    public void addProduct(Product product) {  
        products.add(product);  
    }  
  
    // 创建并返回产品迭代器  
    @Override  
    public Iterator<Product> iterator() {  
        return new ProductIterator(products);  
    }  
  
    // 私有内部类实现迭代器接口  
    private class ProductIterator implements Iterator<Product> {  
        private List<Product> productList;  
        private int position;  
  
        public ProductIterator(List<Product> productList) {  
            this.productList = productList;  
        }  
  
        @Override  
        public boolean hasNext() {  
            return position < productList.size();  
        }  
  
        @Override  
        public Product next() {  
            if (hasNext()) {  
                return productList.get(position++);  
            } else {  
                return null; // 或者抛出异常  
            }  
        }  
    }  
}  
  
// 客户端代码  
public class Client {  
    public static void main(String[] args) {  
        ProductCatalog catalog = new ProductCatalog();  
        catalog.addProduct(new Product("Laptop", 999.99));  
        catalog.addProduct(new Product("Smartphone", 499.99));  
        catalog.addProduct(new Product("Tablet", 299.99));  
  
        // 使用迭代器遍历产品目录  
        for (Product product : catalog) {  
            System.out.println(product);  
        }  
  
        // 客户端代码不依赖于ProductCatalog的内部实现,  
        // 如果内部实现发生变化,客户端代码不需要修改。  
    }  
}

运行上面的代码会得到以下输出:

java 复制代码
Product{name='Laptop', price=999.99}  
Product{name='Smartphone', price=499.99}  
Product{name='Tablet', price=299.99}

在这个例子中,ProductCatalog类实现了Iterable接口,并提供了一个iterator方法来创建并返回ProductIterator对象,ProductIteratorProductCatalog的一个私有内部类,它实现了Iterator接口来遍历产品列表,client代码使用增强的for循环(它内部使用了迭代器)来遍历ProductCatalog中的产品,而不需要知道其内部是如何存储产品的。

这个实现遵循了迭代器模式,允许client代码以统一的方式遍历聚合对象,而不需要了解聚合对象的内部表示,使得client代码与聚合对象之间实现了松耦合,符合开闭原则。

核心总结

迭代器模式是一种常用的设计模式,它使程序能够遍历聚合对象的元素而无需暴露其内部表示,它支持对聚合对象的多种遍历,简化了聚合类,符合单一职责原则,使client代码与聚合对象的具体实现解耦,增强了系统的可扩展性,当需要新增遍历方式时,只需增加新的迭代器类,不影响原有代码;迭代器模式会涉及到较多的类,可能会增加系统的复杂性和开发成本,同时,由于它将存储数据和遍历数据的职分离,可能在某些情况下导致额外的性能开销,在需要遍历复杂数据结构或支持多种遍历方式时,推荐使用迭代器模式,但要注意权衡系统复杂性和灵活性,避免过度设计,同时,可以考虑使用Java内置的迭代器接口和工具类来简化实现。

相关推荐
javaDocker18 分钟前
业务架构、数据架构、应用架构和技术架构
架构
新知图书22 分钟前
Rust编程与项目实战-模块std::thread(之一)
开发语言·后端·rust
盛夏绽放44 分钟前
Node.js 和 Socket.IO 实现实时通信
前端·后端·websocket·node.js
Ares-Wang1 小时前
Asp.net Core Hosted Service(托管服务) Timer (定时任务)
后端·asp.net
JosieBook2 小时前
【架构】主流企业架构Zachman、ToGAF、FEA、DoDAF介绍
架构
Rverdoser2 小时前
RabbitMQ的基本概念和入门
开发语言·后端·ruby
小白不太白9503 小时前
设计模式之 模板方法模式
java·设计模式·模板方法模式
Tech Synapse3 小时前
Java根据前端返回的字段名进行查询数据的方法
java·开发语言·后端
.生产的驴3 小时前
SpringCloud OpenFeign用户转发在请求头中添加用户信息 微服务内部调用
spring boot·后端·spring·spring cloud·微服务·架构