Iterator
迭代器模式是一种行为设计模式, 让你能在不暴露集合底层表现形式 (列表、 栈和树等) 的情况下遍历集合中所有的元素。
下面是该模式在核心 Java 程序库中的一些示例:
- java.util.Iterator的所有实现 (还有 java.util.Scanner)。
- java.util.Enumeration的所有实现
识别方法: 迭代器可以通过导航方法 (例如 next
和 previous
等) 来轻松识别。 使用迭代器的客户端代码可能没有其所遍历的集合的直接访问权限。
迭代器模式结构
样例
迭代器模式被用于在不向客户端代码暴露通信细节的情况下访问远程社交网络集合中的社交媒体档案。
迭代器接口
package behavioral.iterator.iterators;
import behavioral.iterator.Profile;
/**
* 定义档案接口
*/
public interface ProfileIterator {
boolean hasNext();
Profile getNext();
void reset();
}
迭代器具体实现
package behavioral.iterator.iterators;
import behavioral.iterator.Profile;
import behavioral.iterator.social_networks.Facebook;
import java.util.ArrayList;
import java.util.List;
/**
* 在 Facebook 档案上实现迭代
*/
public class FacebookIterator implements ProfileIterator {
private Facebook facebook;
private String type;
private String email;
private int currentPosition = 0;
private List<String> emails = new ArrayList<>();
private List<Profile> profiles = new ArrayList<>();
public FacebookIterator(Facebook facebook, String type, String email) {
this.facebook = facebook;
this.type = type;
this.email = email;
}
private void lazyLoad() {
if (emails.size() == 0) {
List<String> profiles = facebook.requestProfileFriendsFromFacebook(this.email, this.type);
for (String profile : profiles) {
this.emails.add(profile);
this.profiles.add(null);
}
}
}
@Override
public boolean hasNext() {
lazyLoad();
return currentPosition < emails.size();
}
@Override
public Profile getNext() {
if (!hasNext()) {
return null;
}
String friendEmail = emails.get(currentPosition);
Profile friendProfile = profiles.get(currentPosition);
if (friendProfile == null) {
friendProfile = facebook.requestProfileFromFacebook(friendEmail);
profiles.set(currentPosition, friendProfile);
}
currentPosition++;
return friendProfile;
}
@Override
public void reset() {
currentPosition = 0;
}
}
通用的社交网络接口
package behavioral.iterator.social_networks;
import behavioral.iterator.iterators.ProfileIterator;
/**
* 定义通用的社交网络接口
* 声明一个用于生成迭代器的工厂方法
*/
public interface SocialNetwork {
ProfileIterator createFriendsIterator(String profileEmail);
ProfileIterator createCoworkersIterator(String profileEmail);
}
package behavioral.iterator.social_networks;
import behavioral.iterator.Profile;
import behavioral.iterator.iterators.FacebookIterator;
import behavioral.iterator.iterators.ProfileIterator;
import java.util.ArrayList;
import java.util.List;
public class Facebook implements SocialNetwork {
private List<Profile> profiles;
public Facebook(List<Profile> profiles) {
if (profiles != null) {
this.profiles = profiles;
} else {
this.profiles = new ArrayList<>();
}
}
public Profile requestProfileFromFacebook(String profileEmail) {
// Here would be a POST request to one of the Facebook API endpoints.
// Instead, we emulates long network connection, which you would expect
// in the real life...
simulateNetworkLatency();
System.out.println("Facebook: Loading profile '" + profileEmail + "' over the network...");
// ...and return test data.
return findProfile(profileEmail);
}
public List<String> requestProfileFriendsFromFacebook(String profileEmail, String contactType) {
// Here would be a POST request to one of the Facebook API endpoints.
// Instead, we emulates long network connection, which you would expect
// in the real life...
simulateNetworkLatency();
System.out.println("Facebook: Loading '" + contactType + "' list of '" + profileEmail + "' over the network...");
// ...and return test data.
Profile profile = findProfile(profileEmail);
if (profile != null) {
return profile.getContacts(contactType);
}
return null;
}
private Profile findProfile(String profileEmail) {
for (Profile profile : profiles) {
if (profile.getEmail().equals(profileEmail)) {
return profile;
}
}
return null;
}
private void simulateNetworkLatency() {
try {
Thread.sleep(2500);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
@Override
public ProfileIterator createFriendsIterator(String profileEmail) {
return new FacebookIterator(this, "friends", profileEmail);
}
@Override
public ProfileIterator createCoworkersIterator(String profileEmail) {
return new FacebookIterator(this, "coworkers", profileEmail);
}
}
社交档案
package behavioral.iterator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 社交档案
*/
public class Profile {
private String name;
private String email;
private Map<String, List<String>> contacts = new HashMap<>();
public Profile(String email, String name, String... contacts) {
this.email = email;
this.name = name;
//Parse contact list from a set of "friend:email@gmail.com" pairs.
for (String contact : contacts) {
String[] parts = contact.split(":");
String contactType = "friend";
String contactEmail;
if (parts.length == 1) {
contactEmail = parts[0];
} else {
contactType = parts[0];
contactEmail = parts[1];
}
if (!this.contacts.containsKey(contactType)) {
this.contacts.put(contactType, new ArrayList<>());
}
this.contacts.get(contactType).add(contactEmail);
}
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
public List<String> getContacts(String contactType) {
if (!this.contacts.containsKey(contactType)) {
this.contacts.put(contactType, new ArrayList<>());
}
return contacts.get(contactType);
}
}
消息发送应用
package behavioral.iterator;
import behavioral.iterator.iterators.ProfileIterator;
import behavioral.iterator.social_networks.SocialNetwork;
public class SocialSpammer {
public SocialNetwork network;
private ProfileIterator iterator;
public SocialSpammer(SocialNetwork network) {
this.network = network;
}
public void sendSpamToFriends(String profileEmail,String message){
System.out.println("\nIterating over friends...\n");
iterator = network.createFriendsIterator(profileEmail);
while (iterator.hasNext()) {
Profile profile = iterator.getNext();
sendMessage(profile.getEmail(), message);
}
}
public void sendSpamToCoworkers(String profileEmail, String message) {
System.out.println("\nIterating over coworkers...\n");
iterator = network.createCoworkersIterator(profileEmail);
while (iterator.hasNext()) {
Profile profile = iterator.getNext();
sendMessage(profile.getEmail(), message);
}
}
public void sendMessage(String email, String message) {
System.out.println("Sent message to: '" + email + "'. Message body: '" + message + "'");
}
}
测试
package behavioral2.iterator;
import behavioral2.iterator.social_networks.Facebook;
import behavioral2.iterator.social_networks.LinkedIn;
import behavioral2.iterator.social_networks.SocialNetwork;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Demo {
public static Scanner scanner = new Scanner(System.in);
public static void main(String[] args) {
System.out.println("Please specify social network to target spam tool (default:Facebook):");
System.out.println("1. Facebook");
System.out.println("2. LinkedIn");
String choice = scanner.nextLine();
SocialNetwork network;
if (choice.equals("2")) {
network = new LinkedIn(createTestProfiles());
}
else {
network = new Facebook(createTestProfiles());
}
SocialSpammer spammer = new SocialSpammer(network);
spammer.sendSpamToFriends("anna.smith@bing.com",
"Hey! This is Anna's friend Josh. Can you do me a favor and like this post [link]?");
spammer.sendSpamToCoworkers("anna.smith@bing.com",
"Hey! This is Anna's boss Jason. Anna told me you would be interested in [link].");
}
public static List<Profile> createTestProfiles() {
List<Profile> data = new ArrayList<>();
data.add(new Profile("anna.smith@bing.com", "Anna Smith", "friends:mad_max@ya.com",
"friends:catwoman@yahoo.com", "coworkers:sam@amazon.com"));
data.add(new Profile("mad_max@ya.com", "Maximilian", "friends:anna.smith@bing.com",
"coworkers:sam@amazon.com"));
data.add(new Profile("bill@microsoft.eu", "Billie", "coworkers:avanger@ukr.net"));
data.add(new Profile("avanger@ukr.net", "John Day", "coworkers:bill@microsoft.eu"));
data.add(new Profile("sam@amazon.com", "Sam Kitting", "coworkers:anna.smith@bing.com",
"coworkers:mad_max@ya.com", "friends:catwoman@yahoo.com"));
data.add(new Profile("catwoman@yahoo.com", "Liza", "friends:anna.smith@bing.com",
"friends:sam@amazon.com"));
return data;
}
}
/**
*Please specify social network to target spam tool (default:Facebook):
* 1. Facebook
* 2. LinkedIn
* 1
*
* Iterating over friends...
*
* Facebook: Loading 'friends' list of 'anna.smith@bing.com' over the network...
* Facebook: Loading profile 'mad_max@ya.com' over the network...
* Sent message to: 'mad_max@ya.com'. Message body: 'Hey! This is Anna's friend Josh. Can you do me a favor and like this post [link]?'
* Facebook: Loading profile 'catwoman@yahoo.com' over the network...
* Sent message to: 'catwoman@yahoo.com'. Message body: 'Hey! This is Anna's friend Josh. Can you do me a favor and like this post [link]?'
*
* Iterating over coworkers...
*
* Facebook: Loading 'coworkers' list of 'anna.smith@bing.com' over the network...
* Facebook: Loading profile 'sam@amazon.com' over the network...
* Sent message to: 'sam@amazon.com'. Message body: 'Hey! This is Anna's boss Jason. Anna told me you would be interested in [link].'
*/
适用场景
-
当集合背后为复杂的数据结构, 且你希望对客户端隐藏其复杂性时 (出于使用便利性或安全性的考虑), 可以使用迭代器模式。
迭代器封装了与复杂数据结构进行交互的细节, 为客户端提供多个访问集合元素的简单方法。 这种方式不仅对客户端来说非常方便, 而且能避免客户端在直接与集合交互时执行错误或有害的操作, 从而起到保护集合的作用。
-
使用该模式可以减少程序中重复的遍历代码。
重要迭代算法的代码往往体积非常庞大。 当这些代码被放置在程序业务逻辑中时, 它会让原始代码的职责模糊不清, 降低其可维护性。 因此, 将遍历代码移到特定的迭代器中可使程序代码更加精炼和简洁。
-
如果你希望代码能够遍历不同的甚至是无法预知的数据结构, 可以使用迭代器模式。
该模式为集合和迭代器提供了一些通用接口。 如果你在代码中使用了这些接口, 那么将其他实现了这些接口的集合和迭代器传递给它时, 它仍将可以正常运行。
实现方式
- 声明迭代器接口。 该接口必须提供至少一个方法来获取集合中的下个元素。 但为了使用方便, 你还可以添加一些其他方法, 例如获取前一个元素、 记录当前位置和判断迭代是否已结束。
- 声明集合接口并描述一个获取迭代器的方法。 其返回值必须是迭代器接口。 如果你计划拥有多组不同的迭代器, 则可以声明多个类似的方法。
- 为希望使用迭代器进行遍历的集合实现具体迭代器类。 迭代器对象必须与单个集合实体链接。 链接关系通常通过迭代器的构造函数建立。
- 在你的集合类中实现集合接口。 其主要思想是针对特定集合为客户端代码提供创建迭代器的快捷方式。 集合对象必须将自身传递给迭代器的构造函数来创建两者之间的链接。
- 检查客户端代码, 使用迭代器替代所有集合遍历代码。 每当客户端需要遍历集合元素时都会获取一个新的迭代器。
迭代器优点
- 单一职责原则。 通过将体积庞大的遍历算法代码抽取为独立的类, 你可对客户端代码和集合进行整理。
- 开闭原则。 你可实现新型的集合和迭代器并将其传递给现有代码, 无需修改现有代码。
- 你可以并行遍历同一集合, 因为每个迭代器对象都包含其自身的遍历状态。
- 相似的, 你可以暂停遍历并在需要时继续。
迭代器缺点
- 如果你的程序只与简单的集合进行交互,应用该模式可能会矫枉过正。
- 对于某些特殊集合, 使用迭代器可能比直接遍历的效率低。