[toc]

设计模式之观察者模式

## 1. 再谈设计准则

1.1 可维护性(Maintainability):

  • 可扩展性;
  • 灵活性;
  • 可插拔;

1.2 可复用性(Reuseability):

  • 代码复用;
  • 算法复用;
  • 数据结构复用;

1.3 可维护性与可复用性的关系:

1.4 六大设计准则

1. 开闭准则-OCP(open-close-Principle)

软件系统的设计, 应答扩大凋谢, 对批改敞开;

2. 里氏代换准则-LSP(Liskov-Substitution-Principle)

所有基类呈现的中央, 都能够被子类替换;

3. 依赖倒置准则-DIP(Dependence-Inversion-Principle)

应该依赖于形象而非具体;

4. 接口隔离准则-ISP(Interface-Segregation-Principle)

多个专门的接口好于繁多的总接口;

5.合成/聚合复用准则-CARP(Composite/Aggregate-Reuse-Principle)

应用合成/聚合的形式复用, 而不是继承;聚合: 更强的合成;

6. 迪米特法令-LoD(Law-of-Demeter)

若非必要,不要裸露(起码晓得准则);

2. 观察者模式

2.1 始于回调(回调模式)

public class App {    // 主业务流程    public void doBusiness(ICallback callback){        System.out.println("主流程工作...");        callback.call();    }}public interface ICallback {    void call();}// 0. 回调操作随主流程执行@Testvoid callbackTest() {    App app = new App();    app.doBusiness(() -> {        System.out.println("回调操作...");    });}

2.2 观察者模式-类图

Subject/OneSubject: 被察看对象(发布者)
Observer: 观察者(订阅者)

  1. 观察者接口(订阅者)

    /** * 观察者(订阅者) */public interface Observer { void update(String msg);}
  2. 观察者/订阅者1(实现类)

    /** * 观察者/订阅者1 * @author nieweijun * @since 2021/6/1 22:09 */public class OneObserver implements Observer{ @Override public void update(String msg) {     System.out.println("OneObserver收到音讯:["+msg+"]"); }}
  3. 观察者实现类2

    /** * 观察者/订阅者1 * @author nieweijun * @since 2021/6/1 22:09 */public class OneObserver implements Observer{ @Override public void update(String msg) {     System.out.println("OneObserver收到音讯:["+msg+"]"); }}
  4. 被观察者

    /** * 被观察者(主题/发布者) */public interface Subject { void addObserver(Observer observer); void removeObserver(Observer observer); void notifyObservers();}
  5. 被观察者实现

    /** * 主题/发布者实现 * @author nieweijun * @since 2021/6/1 22:12 */public class OneSubject implements Subject{ Vector<Observer> observers = null; public OneSubject(){     this.observers = new Vector<>(); } @Override public void addObserver(Observer observer) {     observers.addElement(observer); } @Override public void removeObserver(Observer observer) {     observers.removeElement(observer); } @Override public void notifyObservers() {     for (Observer observer : observers) {         observer.update("雷阵雨~");     } }}
  6. 测试调用:

    // 1.一般观察者 @Test void myObserverTest() {     // 1.被观察者(主题)     OneSubject subject = new OneSubject();     // 2.观察者1(订阅者1)     OneObserver obs1 = new OneObserver();     // 3.观察者2(订阅者2)     TwoObserver obs2 = new TwoObserver();     // 注册观察者     subject.addObserver(obs1);     subject.addObserver(obs2);     // 公布音讯(告诉所有观察者)     subject.notifyObservers(); }
    OneObserver收到音讯:[雷阵雨~]TwoObserver收到音讯:[雷阵雨~]

2.3 JDK中的观察者

Observable/MySubject: 被察看对象(发布者)
Observer: 观察者(订阅者)

  1. jdk中的发布者实现类(被观察者)

    import java.util.Observable;/** * JDK内置的观察者模式用例 * @author nieweijun * @since 2021/5/31 18:13 */@Slf4jpublic class WeatherObservable extends Observable { @Override public String toString() {     return "布谷天气"; } @Override public synchronized void setChanged() {     super.setChanged(); }}
  2. 测试用例调用

    // 2. jdk观察者 @Test void weatherReportTest() {     WeatherObservable obs = new WeatherObservable(); // 主题     // A     obs.addObserver((o, arg) -> log.info("订阅者-A收到[{}]公布的音讯:{}", o, arg));     // B     obs.addObserver((o, arg) -> log.info("订阅者-B收到音讯[{}]公布的音讯:{}", o, arg));     // C     obs.addObserver((o, arg) -> log.info("订阅者-C收到[{}]公布的音讯:{}", o, arg));     obs.setChanged();     obs.notifyObservers("今日北京雷阵雨"); }

    输入:

    PatternObserverTest - 订阅者-C收到[布谷天气]公布的音讯:今日北京雷阵雨PatternObserverTest - 订阅者-B收到音讯[布谷天气]公布的音讯:今日北京雷阵雨PatternObserverTest - 订阅者-A收到[布谷天气]公布的音讯:今日北京雷阵雨

    留神: 观察者的实现用FunctionalInterface实现了, 3个观察者;

2.4. jdk中的AWT监听器

按钮监听事件案例:

import javax.swing.*;import java.awt.*;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;/** * java GUI 观察者(监听器) * @author nieweijun * @since 2021/6/1 22:21 */public class GuiObserver {    public GuiObserver() {        JFrame jFrame = new JFrame("HelloButton");        // 1. button 事件源        JButton btn = new JButton("click me~");        // 2. 事件监听器(携带监听事件 ActionEvent)        ActionListener myListener = new ActionListener() {            @Override            public void actionPerformed(ActionEvent e) {                JOptionPane.showMessageDialog(null, "按钮被点击!");            }        };        // 3. 注册事件        btn.addActionListener(myListener);//        btn.addActionListener((e) -> JOptionPane.showMessageDialog(null, "按钮被点击!"));        jFrame.add(btn);        jFrame.setLayout(new FlowLayout());        jFrame.setSize(500,400);        jFrame.setLocation(400, 400);        jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);        jFrame.setVisible(true);    }    public static void main(String[] args) {        new GuiObserver();    }}
  • ActionListener 监听器(潜藏着事件源)
  • ActionEvent 事件
  • btn.addActionListener 客户端注册事件

3. 监听器

案例: 回家事件; 两个实现(一个是工作日回家; 一个是节假日回家; 输入信息不一样)

3.1. 事件定义(继承EventObject)

import java.util.EventObject;/** * 事件:(个别潜藏于Listener) * @author nieweijun * @since 2021/6/9 9:49 */public class HomeEvent extends EventObject {    @Setter    @Getter    private String type;    public HomeEvent(Object source, String type){        super(source);        this.type = type;    }    /**     * @param source 事件源     * @throws IllegalArgumentException     */    public HomeEvent(Object source) {        super(source);    }}

3.2. 监听器接口定义(继承EventListener)

定义本人的回调接口, 拉上事件做参数
import java.util.EventListener;/** * 回家事件监听器 * @author nieweijun * @since 2021/6/9 9:53 */public interface HomeListener extends EventListener {    void onHomeEvent(HomeEvent event);}

3.3. 监听器接口实现

  • HolidayHomeListener
import lombok.extern.slf4j.Slf4j;/** * 节假日回家事件 * @author nieweijun * @since 2021/6/9 9:56 */@Slf4jpublic class HolidayHomeListener implements HomeListener {    @Override    public void onHomeEvent(HomeEvent event) {        if (event.getType().equals("holiday")) {            log.info("@===============> HolidayHomeListener#onHomeEvent:放假回家, 开开心心的! type={}", event.getType());        }    }}
  • WorkdayHomeListener
import lombok.extern.slf4j.Slf4j;/** * 工作日回家事件 * @author nieweijun * @since 2021/6/9 9:56 */@Slf4jpublic class WorkdayHomeListener implements HomeListener {    @Override    public void onHomeEvent(HomeEvent event) {        if (event.getType().equals("work")) {            log.info("@===============> WorkdayHomeListener#onHomeEvent:工作日回家, 吾日三省吾身了没? type={}", event.getType());        }    }}

3.4. 管理者(Listener容器持有者)

import java.util.ArrayList;import java.util.Iterator;import java.util.List;/** * 事件发布者 * @author nieweijun * @since 2021/6/9 13:49 */public class Manager {    private List<HomeListener> list;    public Manager(){        list = new ArrayList<>();    }    public void addListener(HomeListener listener) {        if(list == null){            list = new ArrayList<>();        }        list.add(listener);    }    public void removeListener(HomeListener listener) {        if(list != null){            list.remove(listener);        }    }    /**     * 告诉所有的Listener     */    private void notifyListeners(HomeEvent event) {        for (HomeListener listener : list) {            listener.onHomeEvent(event);        }    }    public void holidayGoHome(){        HomeEvent event = new HomeEvent(this, "holiday");        notifyListeners(event);    }    public void workdayGoHome(){        HomeEvent event = new HomeEvent(this, "work");        notifyListeners(event);    }}

留神: 两个办法: holidayGoHomeworkdayGoHome 两个办法, 做了公布事件的调用, 并触发告诉事件(告诉所有的监听者)

3.5. 测试用例调用

  // 5. 自定义监听器 测试用例    @Test    void testListener() {        Manager manager = new Manager();        manager.addListener(new HolidayHomeListener());        manager.addListener(new WorkdayHomeListener());        // 工作日回家        manager.workdayGoHome();        // 节假日回家        manager.holidayGoHome();    }

输入:

WorkdayHomeListener - @===============> WorkdayHomeListener#onHomeEvent:工作日回家, 吾日三省吾身了没? type=workHolidayHomeListener - @===============> HolidayHomeListener#onHomeEvent:放假回家, 开开心心的! type=holiday

4. 观察者和监听器比拟

4.1. 事件监听器三要素:

  • 事件源
  • 事件
  • 事件监听器

4.2. 观察者而因素:

  • 被观察者(主题)
  • 观察者(订阅者/接收者/被告诉者)

4.3. 比拟: 如图

事件+事件源的作用就是被观察者;

5. 模式实际经典利用

5.1. Spring事件机制

  • ApplicationListener/@EventListener+ApplicationEvent
  • ApplicationEvent
  • ApplicationEventPublisher

    1. 事件:

    import lombok.Getter;import org.springframework.context.ApplicationEvent;/** * 观察者 * @author nieweijun * @since 2021/5/31 14:15 */public class RegisterEvent extends ApplicationEvent {    /**     * 登录用户用户名     */    @Getter    private String userName;    /**     * Create a new {@code ApplicationEvent}.     * @param source the object on which the event initially occurred or with     *               which the event is associated (never {@code null})     */    public RegisterEvent(Object source) {        super(source);    }    public RegisterEvent(Object source, String userName) {        super(source);        this.userName = userName;    }}

2. 监听器1:

import lombok.extern.slf4j.Slf4j;import org.springframework.context.ApplicationListener;import org.springframework.stereotype.Service;/** * 发放优惠券 * @author nieweijun * @since 2021/5/31 14:25 */@Service@Slf4jpublic class CouponServiceListener implements ApplicationListener<RegisterEvent> {    @Override    public void onApplicationEvent(RegisterEvent event) {        log.info("#=====>[CouponService.onApplicationEvent] 给用户{}发放3张满减老手券! =====#", event.getUserName());    }}

2. 监听器2:

import lombok.extern.slf4j.Slf4j;import org.springframework.context.ApplicationListener;import org.springframework.scheduling.annotation.Async;import org.springframework.stereotype.Service;/** * @author nieweijun * @since 2021/5/31 14:23 */@Service@Slf4jpublic class EmailServiceListener implements ApplicationListener<RegisterEvent> {    @Async // 异步发邮件    @Override    public void onApplicationEvent(RegisterEvent event) {        log.info("#=====>[EmailService.onApplicationEvent] 执行发邮件给用户: {} =====# ", event.getUserName());    }}

2. 监听器3 (注解形式):

import lombok.extern.slf4j.Slf4j;import org.springframework.context.event.EventListener;import org.springframework.stereotype.Service;/** * 新用户告知音讯 * @author nieweijun * @since 2021/5/31 14:29 */@Service@Slf4jpublic class RegisterNoticeService {    @EventListener    public void notice(RegisterEvent event) {        log.info("#=====>[RegisterNoticeService.notice] 音讯告知:{} 你好! 欢送你退出社团, 您须要恪守以下规定 1, 2, 3, 4, 5 =====#", event.getUserName());    }}

3. 事件公布:

import lombok.extern.slf4j.Slf4j;import org.springframework.context.ApplicationEventPublisher;import org.springframework.context.ApplicationEventPublisherAware;import org.springframework.stereotype.Service;@Service@Slf4jpublic class UserBizService implements ApplicationEventPublisherAware {    private ApplicationEventPublisher applicationEventPublisher;    @Override    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {        this.applicationEventPublisher = applicationEventPublisher;    }    public void register(String userName){        // 1. 执行注册逻辑        log.info("#=====>用户 [{}] 注册逻辑胜利!", userName);        // 2. 公布事件        applicationEventPublisher.publishEvent(new RegisterEvent(this, userName));    }}

4. 测试用例:

import lombok.extern.slf4j.Slf4j;import org.junit.jupiter.api.Test;import org.springframework.boot.test.context.SpringBootTest;import javax.annotation.Resource;/** * 设计模式测试用例 * @author nieweijun * @since 2021/6/1 18:20 */@Slf4j@SpringBootTestpublic class PatternObserverTest {    @Resource    private UserBizService userBizService;        // 3. spring事件监听测试    @Test    void testRegister() { //@SpringBootTest        userBizService.register("niewj");    }    }

5. 执行后果:

listener.UserBizService   : #=====>用户 [niewj] 注册逻辑胜利!listener.RegisterNoticeService   : #=====>[RegisterNoticeService.notice] 音讯告知:niewj 你好! 欢送你退出社团, 您须要恪守以下规定 1, 2, 3, 4, 5 =====#listener.CouponServiceListener   : #=====>[CouponService.onApplicationEvent] 给用户niewj发放3张满减老手券! =====#listener.EmailServiceListener    : #=====>[EmailService.onApplicationEvent] 执行发邮件给用户: niewj =====# 

5.2. GUI之AWT事件监听器

  • ActionListener
  • ActionEvent
  • btn.addActionListener

    1. button单击事件绑定监听

    import javax.swing.*;import java.awt.*;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;/** * java GUI 观察者(监听器) * @author nieweijun * @since 2021/6/1 22:21 */public class GuiObserver {    public GuiObserver() {        JFrame jFrame = new JFrame("HelloButton");        // 1. button 事件源        JButton btn = new JButton("click me~");        // 2. 事件监听器(携带监听事件 ActionEvent)        ActionListener myListener = new ActionListener() {            @Override            public void actionPerformed(ActionEvent e) {                JOptionPane.showMessageDialog(null, "按钮被点击!");            }        };        // 3. 注册事件        btn.addActionListener(myListener);//        btn.addActionListener((e) -> JOptionPane.showMessageDialog(null, "按钮被点击!"));        jFrame.add(btn);        jFrame.setLayout(new FlowLayout());        jFrame.setSize(500,400);        jFrame.setLocation(400, 400);        jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);        jFrame.setVisible(true);    }    public static void main(String[] args) {        new GuiObserver();    }}

执行后果:

5.3. 谷歌Guava之EventBus

  • @subscribe
  • EventBus#register#post

    import com.google.common.eventbus.Subscribe;import lombok.extern.slf4j.Slf4j;/** * 事件监听器 * @author nieweijun * @since 2021/6/9 13:34 */@Slf4jpublic class EventListener {    @Subscribe    public void listenString(String msg){      log.info("#======>EventListener.listenString:{}", msg);    }    @Subscribe    public void listenInteger(Integer num){      log.info("@======>EventListener.listenInteger:{}", num);    }}

调用测试:

// 4. Guava EventBus 简略测试用例@Testvoid testEventBus() {    EventBus eventBus = new EventBus();    // register    eventBus.register(new EventListener());    // post    eventBus.post("somename");    eventBus.post(10);}

输入后果:

eventBus.EventListener - #======>EventListener.listenString:somenameeventBus.EventListener - @======>EventListener.listenInteger:10

6. 参考资料

  • JDK API文档
  • Spring Framework
  • Google Guava EventBus
  • 回调、事件监听器、观察者模式
  • Spring中的观察者模式