关于设计模式:设计模式之观察者模式

57次阅读

共计 11612 个字符,预计需要花费 30 分钟才能阅读完成。

[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. 回调操作随主流程执行
@Test
void 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
     */
    @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 个观察者;

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. 比拟: 如图

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

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();
        }
    }

执行后果:

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. 参考资料

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

正文完
 0