1. 概念
- 迭代器模式是一种行为型设计模式,它提供了一种统一的方式来访问集合对象中的元素。
- 迭代器模式的核心思想是将遍历集合的责任封装到一个单独的对象中,这样可以避免暴露集合内部的表示方式。这种模式通常用于提供一种方法来访问一个容器对象中各个元素,同时又不暴露该对象的内部细节。
2. 原理结构图
- 迭代器(Iterator)角色
- 定义了一个访问聚合对象元素的接口,它通常包含以下方法:next()(返回下一个元素)、hasNext()(检查是否还有更多元素)、remove()(从聚合对象中移除迭代器最后返回的元素)。
- 迭代器接口为遍历聚合对象中的元素提供了统一的方式,使得聚合对象的具体实现与遍历方式解耦。
- 具体迭代器(Concrete Iterator)角色
- 实现迭代器接口,完成对聚合对象的遍历。
- 具体迭代器持有对聚合对象的引用,并通过该引用访问聚合对象中的元素。
- 具体迭代器知道如何遍历聚合对象的元素,例如通过索引、指针或其他内部表示。
- 聚合对象(Aggregate)角色
- 定义创建迭代器对象的接口,通常是一个返回迭代器对象的方法。
- 聚合对象持有多个元素,并提供对元素的访问。
- 聚合对象的具体实现可以是数组、列表、集合等任何可遍历的数据结构。
- 具体聚合对象(Concrete Aggregate)角色
- 实现聚合对象的接口,返回具体迭代器对象。
- 具体聚合对象维护了元素的集合,并提供给迭代器使用。
- 当请求迭代器时,具体聚合对象会创建一个具体迭代器实例,该实例将用于遍历聚合对象中的元素。
3. 代码示例
3.1 示例1-导航系统
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
interface Location {
String getName();
Iterator<Location> getLocations();
}
class Region implements Location {
private String name;
private List<Location> locations;
public Region(String name) {
this.name = name;
this.locations = new ArrayList<>();
}
@Override
public String getName() {
return name;
}
public void addLocation(Location location) {
locations.add(location);
}
@Override
public Iterator<Location> getLocations() {
return new LocationIterator(locations);
}
}
class City implements Location {
private String name;
private List<Location> attractions;
public City(String name) {
this.name = name;
this.attractions = new ArrayList<>();
}
@Override
public String getName() {
return name;
}
public void addAttraction(Location attraction) {
attractions.add(attraction);
}
@Override
public Iterator<Location> getLocations() {
return new LocationIterator(attractions);
}
}
interface Iterator<T> {
boolean hasNext();
T next();
}
class LocationIterator implements Iterator<Location> {
private List<Location> locations;
private int currentIndex = 0;
public LocationIterator(List<Location> locations) {
this.locations = locations;
}
@Override
public boolean hasNext() {
return currentIndex < locations.size();
}
@Override
public Location next() {
if (hasNext()) {
return locations.get(currentIndex++);
} else {
throw new NoSuchElementException();
}
}
}
class EmptyIterator<T> implements Iterator<T> {
@Override
public boolean hasNext() {
return false;
}
@Override
public T next() {
throw new NoSuchElementException();
}
}
class Attraction implements Location {
private String name;
public Attraction(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public Iterator<Location> getLocations() {
return new EmptyIterator<>();
}
}
public class NavigationSystemDemo {
public static void main(String[] args) {
Region region = new Region("华北地区");
City cityA = new City("北京");
Attraction attraction1 = new Attraction("故宫");
Attraction attraction2 = new Attraction("天安门");
cityA.addAttraction(attraction1);
cityA.addAttraction(attraction2);
region.addLocation(cityA);
Iterator<Location> iterator = region.getLocations();
while (iterator.hasNext()) {
Location location = iterator.next();
if (location instanceof City) {
Iterator<Location> attractionsIterator = location.getLocations();
while (attractionsIterator.hasNext()) {
Location attraction = attractionsIterator.next();
System.out.println(attraction.getName());
}
}
}
}
}
故宫
天安门
- 在这个示例中,创建了一个
Region
对象表示地区,并在其中添加了一个City
对象表示城市。城市下面添加了两个Attraction
对象表示景点。Location
接口定义了位置的基本行为,包括获取名称和获取子位置的迭代器。LocationIterator
类实现了迭代器接口,用于遍历位置的子元素。
- 在
NavigationSystemDemo
的main
方法中,创建了一个地区对象,并向其中添加了一个城市对象和两个景点对象。然后,使用迭代器来遍历地区下的所有位置。由于只对城市下的景点感兴趣,所以在遍历到城市时,进一步获取城市的迭代器,并遍历其下的景点,输出景点的名称。
- 需要注意的是,在这个例子中,假设城市下直接包含景点,而没有再进一步的层级结构。如果实际场景中城市下还有更复杂的层级结构(如区域、街道等),那么需要相应地调整City类和迭代器的实现,以支持更深层次的遍历。
3.2 示例2-网络爬虫
- 实现一个简单的网络爬虫,用于遍历和获取一个网站的所有链接
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
class Link {
private String url;
private String title;
public Link(String url, String title) {
this.url = url;
this.title = title;
}
public String getUrl() {
return url;
}
public String getTitle() {
return title;
}
@Override
public String toString() {
return "Link{" +
"url='" + url + '\'' +
", title='" + title + '\'' +
'}';
}
}
class WebPage {
private String url;
private List<Link> links;
public WebPage(String url) {
this.url = url;
this.links = new ArrayList<>();
}
public void addLink(Link link) {
links.add(link);
}
public List<Link> getLinks() {
return links;
}
public String getUrl() {
return url;
}
}
interface LinkIterator extends Iterator<Link> {
}
class WebPageLinkIterator implements LinkIterator {
private Iterator<Link> linkIterator;
public WebPageLinkIterator(List<Link> links) {
this.linkIterator = links.iterator();
}
@Override
public boolean hasNext() {
return linkIterator.hasNext();
}
@Override
public Link next() {
return linkIterator.next();
}
@Override
public void remove() {
linkIterator.remove();
}
}
class WebCrawler {
private List<WebPage> visitedPages = new ArrayList<>();
public void crawl(String startUrl) {
WebPage currentPage = fetchPage(startUrl);
if (currentPage != null) {
visitedPages.add(currentPage);
crawlLinks(currentPage.getLinks());
}
}
private void crawlLinks(List<Link> links) {
for (Link link : links) {
WebPage linkedPage = fetchPage(link.getUrl());
if (!visitedPages.contains(linkedPage)) {
visitedPages.add(linkedPage);
}
}
}
private WebPage fetchPage(String url) {
WebPage page = new WebPage(url);
page.addLink(new Link("http://example.com/link1", "Link 1"));
page.addLink(new Link("http://example.com/link2", "Link 2"));
return page;
}
public void printVisitedLinks() {
for (WebPage page : visitedPages) {
LinkIterator iterator = new WebPageLinkIterator(page.getLinks());
while (iterator.hasNext()) {
Link link = iterator.next();
System.out.println("Visited link: " + link);
}
}
}
}
public class CrawlerDemo {
public static void main(String[] args) {
WebCrawler crawler = new WebCrawler();
String startUrl = "http://example.com";
crawler.crawl(startUrl);
crawler.printVisitedLinks();
}
}
Visited link: Link{url='http://example.com/link1', title='Link 1'}
Visited link: Link{url='http://example.com/link2', title='Link 2'}
Visited link: Link{url='http://example.com/link1', title='Link 1'}
Visited link: Link{url='http://example.com/link2', title='Link 2'}
Visited link: Link{url='http://example.com/link1', title='Link 1'}
Visited link: Link{url='http://example.com/link2', title='Link 2'}
- 上面的
CrawlerDemo
类中,创建了一个WebCrawler
实例,并调用crawl
方法开始从指定的起始URL进行网络爬虫。之后,我们调用printVisitedLinks
方法来打印所有访问过的链接。
4. 优缺点
- 主要作用
- 将集合的遍历行为从集合对象中分离出来,使得客户端可以一致的方式访问不同类型的集合。
- 优点
- 简化遍历:通过提供统一的遍历接口,简化了对聚合对象的访问过程。
- 解耦遍历逻辑:将遍历逻辑与聚合对象的内部表示分离,降低耦合度。
- 多态遍历:支持多种遍历方式,提高灵活性。
- 扩展性:可以方便地添加新的遍历方式,无需修改聚合对象。
- 代码复用:迭代器可复用,减少重复代码。
- 缺点
- 系统复杂性增加:每当新增一个集合类,就需要增加相应的迭代器类,这会导致类的个数成对增加,增加了系统的复杂性。
- 实现成本提高:由于需要为每个集合类配备对应的迭代器,这可能会增加实现的成本和工作量。
- 维护难度加大:随着类的数量增加,维护和管理这些类的难度也随之增大。
- 错误使用风险:如果不正确使用迭代器(如忘记关闭或未完全遍历),可能导致资源泄漏或数据不一致。
5. 应用场景
5.1 主要包括以下几个方面
- 访问复杂数据结构:当需要遍历的数据结构较为复杂时,如栈、树、图等,迭代器模式可以提供一种统一的方式来访问这些结构中的元素,而不需要暴露其内部实现细节。
- 支持多种遍历方式:如果一个集合需要支持多种遍历方式,例如深度优先或广度优先遍历,迭代器模式可以轻松地为每种遍历方式提供一个具体的迭代器实现。
- 扩展性和维护性:当系统需要添加新的集合类型或者修改现有集合类型的遍历方式时,迭代器模式可以简化这一过程,因为新增或修改的代码主要集中在迭代器类中,而不是集合类本身。
- 解耦客户端和集合类:迭代器模式通过抽象的迭代器接口,使得客户端代码与集合类的实现细节解耦,这样即使集合的内部结构发生变化,也不会影响到客户端代码。
- 统一的元素访问方式:不同的集合可能有不同的内部结构和遍历方式,迭代器模式确保了客户端可以通过一致的接口来访问集合中的元素,提高了代码的可读性和可维护性。
5.2 实际应用
- 导航系统:在地图或导航系统中,可以使用迭代器模式来遍历不同的路线或路径,以便找到最佳的导航方案。
- 游戏开发:在游戏中,可以使用迭代器模式来遍历游戏世界中的不同元素,如角色、道具、敌人等。
- 数据处理:在处理大量数据时,如数据库记录、文件内容等,可以使用迭代器模式来逐行或逐个元素地读取和处理数据。
- 网络爬虫:在网页链接的遍历中,迭代器模式使得爬虫能够专注于链接处理,而非其内部实现。
- …
6. JDK中的使用
- Java集合框架
- 迭代器接口
- 在JDK中,迭代器模式的核心是Iterator接口。这个接口定义了遍历集合元素所需的基本方法,包括:
- hasNext():检查是否还有更多元素可以迭代。
- next():返回迭代的下一个元素。
- remove():从底层集合中移除由最后一次调用next()方法返回的元素(可选操作)。
- 集合类中的迭代器实现
- Java集合框架中的每个集合类(如ArrayList、HashSet等)都实现了Iterable接口,该接口要求实现一个返回迭代器的方法iterator()。这样,客户端代码就可以通过调用集合对象的iterator()方法来获取一个迭代器,进而遍历集合中的元素。
7. 注意事项
- 避免外部修改:在迭代过程中,应避免通过外部操作修改被迭代的集合,这可能导致迭代器失效或产生不可预期的结果。
- 迭代器状态管理:迭代器通常具有内部状态,用于跟踪当前迭代的位置。应确保迭代器的状态管理正确,避免状态混乱或越界访问。
- 线程安全:在多线程环境下使用迭代器时,需要注意线程安全问题。可能需要采取同步措施来确保迭代过程的正确性和一致性。
- 空集合处理:对于空集合,迭代器应能够正确处理,避免在迭代过程中抛出异常或产生错误。
- 接口一致性:确保迭代器的接口设计一致,使得客户端代码可以无缝地切换到不同的迭代器实现,提高代码的可维护性和可扩展性。
8. 迭代器模式 VS 组合模式
模式 |
类型 |
目的 |
模式架构核心角色 |
应用场景 |
迭代器模式 |
行为型 |
统一的方法来遍历不同类型的集合 |
迭代器接口(Iterator) |
遍历集合元素的场景 |
组合模式 |
结构型 |
一致的方式处理个别对象和组合对象 |
组件(Component)和复合对象(Composite) |
表示对象的部分-整体层次结构 |