观察者模式

目的

当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式。

解释

示例

案例:购物

假如你有两种类型的对象: 顾客商店 。 顾客对某个特定品牌的产品非常感兴趣 (例如最新型号的 iPhone 手机), 而该产品很快将会在商店里出售。

顾客可以每天来商店看看产品是否到货。 但如果商品尚未到货时, 绝大多数来到商店的顾客都会空手而归。

另一方面, 每次新产品到货时, 商店可以向所有顾客发送邮件 (可能会被视为垃圾邮件)。 这样, 部分顾客就无需反复前往商店了, 但也可能会惹恼对新产品没有兴趣的其他顾客。

我们似乎遇到了一个矛盾: 要么让顾客浪费时间检查产品是否到货, 要么让商店浪费资源去通知没有需求的顾客。

其实可以建立订阅机制,让每个对象都能订阅或取消订阅发布者事件流。

UML 图

image-20221219222817600

基础发布者

public class EventManager {
    Map<String, List<EventListener>> listeners = new HashMap<>();
 
    public EventManager(String... operations) {
        for (String operation : operations) {
            this.listeners.put(operation, new ArrayList<>());
        }
    }
 
    public void subscribe(String eventType, EventListener listener) {
        List<EventListener> users = listeners.get(eventType);
        users.add(listener);
    }
 
    public void unsubscribe(String eventType, EventListener listener) {
        List<EventListener> users = listeners.get(eventType);
        users.remove(listener);
    }
 
    public void notify(String eventType, File file) {
        List<EventListener> users = listeners.get(eventType);
        for (EventListener listener : users) {
            listener.update(eventType, file);
        }
    }
}

具体发布者,由其他对象跟踪

public class Editor {
    public EventManager events;
    private File file;
 
    public Editor() {
        this.events = new EventManager("open", "save");
    }
 
    public void openFile(String filePath) {
        this.file = new File(filePath);
        events.notify("open", file);
    }
 
    public void saveFile() throws Exception {
        if (this.file != null) {
            events.notify("save", file);
        } else {
            throw new Exception("Please open a file first.");
        }
    }
}

观察者

// 基础观察者
public interface EventListener {
    void update(String eventType, File file);
}
 
// 邮件通知观察者
public class EmailNotificationListener implements EventListener {
    private String email;
 
    public EmailNotificationListener(String email) {
        this.email = email;
    }
 
    @Override
    public void update(String eventType, File file) {
        System.out.println("Email to " + email + ": Someone has performed " + eventType + " operation with the following file: " + file.getName());
    }
}

主程序

    public static void main(String[] args) {
        Editor editor = new Editor();
        editor.events.subscribe("save", new EmailNotificationListener("admin@example.com"));
 
        try {
            editor.openFile("test.txt");
            editor.saveFile();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }