## 1. 再談設計原則html
軟件系統的設計, 應對擴展開放, 對修改關閉;
全部基類出現的地方, 均可以被子類替換;
應該依賴於抽象而非具體;
多個專門的接口好於單一的總接口;
使用合成/聚合的方式複用, 而不是繼承; 聚合: 更強的合成;
若非必要,不要暴露(最少知道原則);
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("回調操做..."); }); }
Subject/OneSubject: 被觀察對象(發佈者)
Observer: 觀察者(訂閱者)java
觀察者接口(訂閱者)算法
/** * 觀察者(訂閱者) */ public interface Observer { void update(String msg); }
觀察者/訂閱者1(實現類)spring
/** * 觀察者/訂閱者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+"]"); } }
觀察者實現類2shell
/** * 觀察者/訂閱者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(); }
被觀察者實現api
/** * 主題/發佈者實現 * @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收到消息:[雷陣雨~]
Observable/MySubject: 被觀察對象(發佈者)
Observer: 觀察者(訂閱者)app
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個觀察者;
按鈕監聽事件案例:
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(); } }
案例: 回家事件; 兩個實現(一個是工做日回家; 一個是節假日回家; 輸出信息不同)
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); } }
定義本身的回調接口, 拉上事件作參數
import java.util.EventListener; /** * 回家事件監聽器 * @author nieweijun * @since 2021/6/9 9:53 */ public interface HomeListener extends EventListener { void onHomeEvent(HomeEvent event); }
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()); } } }
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()); } } }
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
兩個方法, 作了發佈事件的調用, 並觸發通知事件(通知全部的監聽者)
// 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
事件+事件源的做用就是被觀察者;
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 =====#
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(); } }
執行結果:
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