1. 程式人生 > 其它 >迭代器模式(學習筆記)

迭代器模式(學習筆記)

  1. 意圖

  提供一種方法順序訪問一個聚合物件中的各個元素,而又不需要暴露該物件內部表示

  2. 動機

  有時候,會使用不同的演算法來遍歷集合中的元素,不斷地向集合中新增遍歷演算法會模糊其高效儲存資料的主要職責。此外,有些演算法可能是根據特定應用訂製的,將其加入到泛型集合類中會顯得非常奇怪。另一方面,使用多種集合的客戶端程式碼可能並不關心儲存資料的方式。不過,由於集合提供不同的元素訪問方式,程式碼將不得不與特定的集合類耦合。迭代器模式的主要思想是將集合的遍歷行為抽取為單獨的迭代器物件。除實現自身演算法外迭代器還封裝了遍歷操作的所有細節例如當前位置和末尾剩餘元素的數量因此多個迭代器可以在相互獨立的情況下同時訪問集合。

迭代器通常會提供一個獲取集合元素的基本方法客戶端可不斷呼叫該方法直至它不返回任何內容這意味著迭代器已經遍歷了所有元素所有迭代器必須實現相同的介面這樣一來只要有合適的迭代器客戶端程式碼就能相容任何型別的集合或遍歷演算法如果你需要採用特殊方式來遍歷集合只需建立一個新的迭代器類即可無需對集合或客戶端進行修改

  3. 適用性

  • 訪問一個聚合物件的內容而無須暴露它的內部表示
  • 支援對聚合物件的多種遍歷
  • 為遍歷不同的聚合結構提供一個統一的介面(即支援多型迭代)

  4. 結構

  5. 效果

  1)單一職責原則通過將體積龐大的遍歷演算法程式碼抽取為獨立的類可對客戶端程式碼和集合進行整理

  2) 開閉原則可以實現新型的集合和迭代器並將其傳遞給現有程式碼無需修改現有程式碼

  3)可以並行遍歷同一集合因為每個迭代器物件都包含其自身的遍歷狀態

  4)如果你的程式只與簡單的集合進行互動應用該模式可能會矯枉過正

  6. 程式碼實現

  iterators/ProfileIterator.java: 定義檔案介面

package iterator.iterators;

import iterator.profile.Profile;

/**
 * @author GaoMing
 * @date 2021/7/19 - 19:24
 */
public interface ProfileIterator {
    
boolean hasNext(); Profile getNext(); void reset(); }

  iterators/FacebookIterator.java: 在 Facebook 檔案上實現迭代

package iterator.iterators;

import iterator.profile.Profile;
import iterator.social_networks.Facebook;

import java.util.ArrayList;
import java.util.List;

/**
 * @author GaoMing
 * @date 2021/7/19 - 19:23
 */
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;
    }
}

  iterators/LinkedInIterator.java: 在領英檔案上實現迭代

package iterator.iterators;

import iterator.profile.Profile;
import iterator.social_networks.LinkedIn;

import java.util.ArrayList;
import java.util.List;

/**
 * @author GaoMing
 * @date 2021/7/19 - 19:24
 */
public class LinkedInIterator implements ProfileIterator{
    private LinkedIn linkedIn;
    private String type;
    private String email;
    private int currentPosition = 0;
    private List<String> emails = new ArrayList<>();
    private List<Profile> contacts = new ArrayList<>();

    public LinkedInIterator(LinkedIn linkedIn, String type, String email) {
        this.linkedIn = linkedIn;
        this.type = type;
        this.email = email;
    }

    private void lazyLoad() {
        if (emails.size() == 0) {
            List<String> profiles = linkedIn.requestRelatedContactsFromLinkedInAPI(this.email, this.type);
            for (String profile : profiles) {
                this.emails.add(profile);
                this.contacts.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 friendContact = contacts.get(currentPosition);
        if (friendContact == null) {
            friendContact = linkedIn.requestContactInfoFromLinkedInAPI(friendEmail);
            contacts.set(currentPosition, friendContact);
        }
        currentPosition++;
        return friendContact;
    }

    @Override
    public void reset() {
        currentPosition = 0;
    }
}

  social_networks/SocialNetwork.java: 定義通用的社交網路介面

package iterator.social_networks;

import iterator.iterators.ProfileIterator;

/**
 * @author GaoMing
 * @date 2021/7/19 - 19:31
 */
public interface SocialNetwork {
    ProfileIterator createFriendsIterator(String profileEmail);

    ProfileIterator createCoworkersIterator(String profileEmail);
}

  social_networks/Facebook.java: Facebook

package iterator.social_networks;

import iterator.iterators.FacebookIterator;
import iterator.iterators.ProfileIterator;
import iterator.profile.Profile;

import java.util.ArrayList;
import java.util.List;

/**
 * @author GaoMing
 * @date 2021/7/19 - 19:30
 */
public class Facebook implements SocialNetwork{
    private List<Profile> profiles;

    public Facebook(List<Profile> cache) {
        if (cache != null) {
            this.profiles = cache;
        } 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);
    }

}

  social_networks/LinkedIn.java: 領英

package iterator.social_networks;

import iterator.iterators.LinkedInIterator;
import iterator.iterators.ProfileIterator;
import iterator.profile.Profile;

import java.util.ArrayList;
import java.util.List;

/**
 * @author GaoMing
 * @date 2021/7/19 - 19:32
 */
public class LinkedIn implements SocialNetwork{
    private List<Profile> contacts;

    public LinkedIn(List<Profile> cache) {
        if (cache != null) {
            this.contacts = cache;
        } else {
            this.contacts = new ArrayList<>();
        }
    }

    public Profile requestContactInfoFromLinkedInAPI(String profileEmail) {
        // Here would be a POST request to one of the LinkedIn API endpoints.
        // Instead, we emulates long network connection, which you would expect
        // in the real life...
        simulateNetworkLatency();
        System.out.println("LinkedIn: Loading profile '" + profileEmail + "' over the network...");

        // ...and return test data.
        return findContact(profileEmail);
    }

    public List<String> requestRelatedContactsFromLinkedInAPI(String profileEmail, String contactType) {
        // Here would be a POST request to one of the LinkedIn API endpoints.
        // Instead, we emulates long network connection, which you would expect
        // in the real life.
        simulateNetworkLatency();
        System.out.println("LinkedIn: Loading '" + contactType + "' list of '" + profileEmail + "' over the network...");

        // ...and return test data.
        Profile profile = findContact(profileEmail);
        if (profile != null) {
            return profile.getContacts(contactType);
        }
        return null;
    }

    private Profile findContact(String profileEmail) {
        for (Profile profile : contacts) {
            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 LinkedInIterator(this, "friends", profileEmail);
    }

    @Override
    public ProfileIterator createCoworkersIterator(String profileEmail) {
        return new LinkedInIterator(this, "coworkers", profileEmail);
    }
}

  profile/Profile.java: 社交檔案

package iterator.profile;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author GaoMing
 * @date 2021/7/19 - 19:25
 */
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 protected]" pairs.
        for (String contact : contacts) {
            String[] parts = contact.split(":");
            String contactType = "friend", 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 getEmail() {
        return email;
    }

    public String getName() {
        return name;
    }

    public List<String> getContacts(String contactType) {
        if (!this.contacts.containsKey(contactType)) {
            this.contacts.put(contactType, new ArrayList<>());
        }
        return contacts.get(contactType);
    }
}

  spammer/SocialSpammer.java: 訊息傳送應用

package iterator.spammer;

import iterator.iterators.ProfileIterator;
import iterator.profile.Profile;
import iterator.social_networks.SocialNetwork;

/**
 * @author GaoMing
 * @date 2021/7/19 - 19:33
 */
public class SocialSpammer {
    public SocialNetwork network;
    public 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 + "'");
    }
}

  Demo.java: 客戶端程式碼

package iterator;

import iterator.profile.Profile;
import iterator.social_networks.Facebook;
import iterator.social_networks.LinkedIn;
import iterator.social_networks.SocialNetwork;
import iterator.spammer.SocialSpammer;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/**
 * @author GaoMing
 * @date 2021/7/19 - 19:34
 */
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("[email protected]",
                "Hey! This is Anna's friend Josh. Can you do me a favor and like this post [link]?");
        spammer.sendSpamToCoworkers("[email protected]",
                "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<Profile>();
        data.add(new Profile("[email protected]", "Anna Smith", "friends:[email protected]", "friends:[email protected]", "coworkers:[email protected]"));
        data.add(new Profile("[email protected]", "Maximilian", "friends:[email protected]", "coworkers:[email protected]"));
        data.add(new Profile("[email protected]", "Billie", "coworkers:[email protected]"));
        data.add(new Profile("[email protected]", "John Day", "coworkers:[email protected]"));
        data.add(new Profile("[email protected]", "Sam Kitting", "coworkers:[email protected]", "coworkers:[email protected]", "friends:[email protected]"));
        data.add(new Profile("[email protected]", "Liza", "friends:[email protected]", "friends:[email protected]"));
        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 '[email protected]' over the network...
Facebook: Loading profile '[email protected]' over the network...
Sent message to: '[email protected]'. Message body: 'Hey! This is Anna's friend Josh. Can you do me a favor and like this post [link]?'
Facebook: Loading profile '[email protected]' over the network...
Sent message to: '[email protected]'. 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 '[email protected]' over the network...
Facebook: Loading profile '[email protected]' over the network...
Sent message to: '[email protected]'. Message body: 'Hey! This is Anna's boss Jason. Anna told me you would be interested in [link].'

  7. 與其他模式的關係

  • 可以使用迭代器模式來遍歷組合模式樹
  • 可以同時使用工廠方法模式和迭代器來讓子類集合返回不同型別的迭代器, 並使得迭代器與集合相匹配
  • 可以同時使用備忘錄模式和迭代器來獲取當前迭代器的狀態, 並且在需要的時候進行回滾
  • 可以同時使用訪問者模式和迭代器來遍歷複雜資料結構,並對其中的元素執行所需操作,即使這些元素所屬的類完全不同

  8. 已知應用  

  • java.util.Iterator的所有實現 (還有 java.util.Scanner)
  • java.util.Enumeration的所有實現

  識別方法:迭代器可以通過導航方法(例如 next和 previous等)來輕鬆識別。使用迭代器的客戶端程式碼可能沒有其所遍歷的集合的直接訪問許可權