设计模式之观察者模式

niewj

[toc]

设计模式之观察者模式

## 1. 再谈设计原则

1.1 可维护性(Maintainability):

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

1.2 可复用性(Reuseability):

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

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

image.png

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. 回调操作随主流程执行
@Test
void callbackTest() {
    App app = new App();
    app.doBusiness(() -> {
        System.out.println("回调操作...");
    });
}

image.png

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收到消息:[雷阵雨~]

image.png

2.3 JDK中的观察者

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

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

    import java.util.Observable;
    
    /**
     * JDK内置的观察者模式用例
     * @author nieweijun
     * @since 2021/5/31 18:13
     */
    @Slf4j
    public 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个观察者;

image.png

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
 */
@Slf4j
public 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
 */
@Slf4j
public 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=work
HolidayHomeListener - @===============> HolidayHomeListener#onHomeEvent:放假回家, 开开心心的! type=holiday

4. 观察者和监听器比较

4.1. 事件监听器三要素:

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

4.2. 观察者而要素:

  • 被观察者(主题)
  • 观察者(订阅者/接收者/被通知者)

4.3. 比较: 如图

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

image.png

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
@Slf4j
public 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
@Slf4j
public 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
@Slf4j
public 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
@Slf4j
public 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
@SpringBootTest
public 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();
        }
    }

执行结果:

image.png

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
     */
    @Slf4j
    public 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 简单测试用例
@Test
void testEventBus() {
    EventBus eventBus = new EventBus();
    // register
    eventBus.register(new EventListener());
    // post
    eventBus.post("somename");
    eventBus.post(10);

}

输出结果:

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

6. 参考资料

阅读 296

遇见超乎想象的自己!

281 声望
8 粉丝
0 条评论
你知道吗?

遇见超乎想象的自己!

281 声望
8 粉丝
文章目录
宣传栏