[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: 观察者(订阅者)
-
观察者接口(订阅者)
/** * 观察者(订阅者) */ public interface Observer {void update(String msg); }
-
观察者 / 订阅者 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+"]"); } }
-
观察者实现类 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+"]"); } }
-
被观察者
/** * 被观察者(主题 / 发布者) */ public interface Subject {void addObserver(Observer observer); void removeObserver(Observer observer); void notifyObservers();}
-
被观察者实现
/** * 主题 / 发布者实现 * @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("雷阵雨~"); } } }
-
测试调用:
// 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: 观察者(订阅者)
-
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. 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);
}
}
留神: 两个办法: holidayGoHome
和 workdayGoHome
两个办法, 做了公布事件的调用, 并触发告诉事件(告诉所有的监听者)
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 中的观察者模式