基於Guava API實現非同步通知和事件回撥

Tom彈架構發表於2021-11-17

本文節選自《設計模式就該這樣學》

1 基於Java API實現通知機制

當小夥伴們在社群提問時,如果有設定指定使用者回答,則對應的使用者就會收到郵件通知,這就是觀察者模式的一種應用場景。有些小夥伴可能會想到MQ、非同步佇列等,其實JDK本身就提供這樣的API。我們用程式碼來還原這樣一個應用場景,首先建立GPer類。


/**
 * JDK提供的一種觀察者的實現方式,被觀察者
 */
public class GPer extends Observable{
    private String name = "GPer生態圈";
    private static GPer gper = null;
    private GPer(){}

    public static GPer getInstance(){
        if(null == gper){
            gper = new GPer();
        }
        return gper;
    }
    public String getName() {
        return name;
    }
    public void publishQuestion(Question question){
        System.out.println(question.getUserName() + "在" + this.name + "上提交了一個問題。");
        setChanged();
        notifyObservers(question);
    }
}

然後建立問題Question類。


public class Question {
    private String userName;
    private String content;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

接著建立老師Teacher類。


public class Teacher implements Observer {

    private String name;

    public Teacher(String name) {
        this.name = name;
    }

    public void update(Observable o, Object arg) {
        GPer gper = (GPer)o;
        Question question = (Question)arg;
        System.out.println("======================");
        System.out.println(name + "老師,你好!\n" + 
"您收到了一個來自" + gper.getName() + "的提問,希望您解答。問題內容如下:\n" +
                   question.getContent() + "\n" + "提問者:" + question.getUserName());
    }
}

最後編寫客戶端測試程式碼。


    public static void main(String[] args) {
        GPer gper = GPer.getInstance();
        Teacher tom = new Teacher("Tom");
        Teacher jerry = new Teacher("Jerry");

        gper.addObserver(tom);
        gper.addObserver(jerry);

        //使用者行為
        Question question = new Question();
        question.setUserName("張三");
        question.setContent("觀察者模式適用於哪些場景?");

        gper.publishQuestion(question);
}

執行結果如下圖所示。

file

2 基於Guava API輕鬆落地觀察者模式

筆者向大家推薦一個實現觀察者模式的非常好用的框架,API使用也非常簡單,舉個例子,首先引入Maven依賴包。


<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>20.0</version>
</dependency>

然後建立偵聽事件GuavaEvent。


/**
 * Created by Tom
 */
public class GuavaEvent {
    @Subscribe
    public void subscribe(String str){
        //業務邏輯
        System.out.println("執行subscribe方法,傳入的引數是:" + str);
    }

}

最後編寫客戶端測試程式碼。



/**
 * Created by Tom
 */
public class GuavaEventTest {
    public static void main(String[] args) {
        EventBus eventbus = new EventBus();
        GuavaEvent guavaEvent = new GuavaEvent();
        eventbus.register(guavaEvent);
        eventbus.post("Tom");
    }

}

3 使用觀察者模式設計滑鼠事件響應API

再來設計一個業務場景,幫助小夥伴們更好地理解觀察者模式。在JDK原始碼中,觀察者模式的應用也非常多。例如java.awt.Event就是觀察者模式的一種,只不過Java很少被用來寫桌面程式。我們用程式碼來實現一下,以幫助小夥伴們更深刻地瞭解觀察者模式的實現原理。首先,建立EventListener介面。


/**
 * 觀察者抽象
 * Created by Tom.
 */
public interface EventListener {

}

建立Event類。



/**
 * 標準事件源格式的定義
 * Created by Tom.
 */
public class Event {
    //事件源,動作是由誰發出的
    private Object source;
    //事件觸發,要通知誰(觀察者)
    private EventListener target;
    //觀察者的回應
    private Method callback;
    //事件的名稱
    private String trigger;
    //事件的觸發事件
    private long time;

    public Event(EventListener target, Method callback) {
        this.target = target;
        this.callback = callback;
    }

    public Object getSource() {
        return source;
    }

    public Event setSource(Object source) {
        this.source = source;
        return this;
    }

    public String getTrigger() {
        return trigger;
    }

    public Event setTrigger(String trigger) {
        this.trigger = trigger;
        return this;
    }

    public long getTime() {
        return time;
    }

    public Event setTime(long time) {
        this.time = time;
        return this;
    }

    public Method getCallback() {
        return callback;
    }

    public EventListener getTarget() {
        return target;
    }

    @Override
    public String toString() {
        return "Event{" +
                "source=" + source +
                ", target=" + target +
                ", callback=" + callback +
                ", trigger='" + trigger + '\'' +
                ", time=" + time +
                '}';
    }
}

建立EventContext類。


/**
 * 被觀察者的抽象
 * Created by Tom.
 */
public abstract class EventContext {
    protected Map<String,Event> events = new HashMap<String,Event>();

    public void addListener(String eventType, EventListener target, Method callback){
        events.put(eventType,new Event(target,callback));
    }

    public void addListener(String eventType, EventListener target){
        try {
            this.addListener(eventType, target, 
target.getClass().getMethod("on"+toUpperFirstCase(eventType), Event.class));
        }catch (NoSuchMethodException e){
            return;
        }
    }

    private String toUpperFirstCase(String eventType) {
        char [] chars = eventType.toCharArray();
        chars[0] -= 32;
        return String.valueOf(chars);
    }

    private void trigger(Event event){
        event.setSource(this);
        event.setTime(System.currentTimeMillis());

        try {
            if (event.getCallback() != null) {
                //用反射呼叫回撥函式
                event.getCallback().invoke(event.getTarget(), event);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    protected void trigger(String trigger){
        if(!this.events.containsKey(trigger)){return;}
        trigger(this.events.get(trigger).setTrigger(trigger));
    }
}

然後建立MouseEventType介面。


/**
 * Created by Tom.
 */
public interface MouseEventType {
    //單擊
    String ON_CLICK = "click";

    //雙擊
    String ON_DOUBLE_CLICK = "doubleClick";

    //彈起
    String ON_UP = "up";

    //按下
    String ON_DOWN = "down";

    //移動
    String ON_MOVE = "move";

    //滾動
    String ON_WHEEL = "wheel";

    //懸停
    String ON_OVER = "over";

    //失去焦點
    String ON_BLUR = "blur";

    //獲得焦點
    String ON_FOCUS = "focus";
}

建立Mouse類。


/**
 * 具體的被觀察者
 * Created by Tom.
 */
public class Mouse extends EventContext {

    public void click(){
        System.out.println("呼叫單擊方法");
        this.trigger(MouseEventType.ON_CLICK);
    }

    public void doubleClick(){
        System.out.println("呼叫雙擊方法");
        this.trigger(MouseEventType.ON_DOUBLE_CLICK);
    }

    public void up(){
        System.out.println("呼叫彈起方法");
        this.trigger(MouseEventType.ON_UP);
    }

    public void down(){
        System.out.println("呼叫按下方法");
        this.trigger(MouseEventType.ON_DOWN);
    }

    public void move(){
        System.out.println("呼叫移動方法");
        this.trigger(MouseEventType.ON_MOVE);
    }

    public void wheel(){
        System.out.println("呼叫滾動方法");
        this.trigger(MouseEventType.ON_WHEEL);
    }

    public void over(){
        System.out.println("呼叫懸停方法");
        this.trigger(MouseEventType.ON_OVER);
    }

    public void blur(){
        System.out.println("呼叫獲得焦點方法");
        this.trigger(MouseEventType.ON_BLUR);
    }

    public void focus(){
        System.out.println("呼叫失去焦點方法");
        this.trigger(MouseEventType.ON_FOCUS);
    }
}

建立回撥方法MouseEventLisenter類。


/**
 * 觀察者
 * Created by Tom.
 */
public class MouseEventListener implements EventListener {


    public void onClick(Event e){
        System.out.println("===========觸發滑鼠單擊事件==========" + "\n" + e);
    }

    public void onDoubleClick(Event e){
        System.out.println("===========觸發滑鼠雙擊事件==========" + "\n" + e);
    }

    public void onUp(Event e){
        System.out.println("===========觸發滑鼠彈起事件==========" + "\n" + e);
    }

    public void onDown(Event e){
        System.out.println("===========觸發滑鼠按下事件==========" + "\n" + e);
    }

    public void onMove(Event e){
        System.out.println("===========觸發滑鼠移動事件==========" + "\n" + e);
    }

    public void onWheel(Event e){
        System.out.println("===========觸發滑鼠滾動事件==========" + "\n" + e);
    }

    public void onOver(Event e){
        System.out.println("===========觸發滑鼠懸停事件==========" + "\n" + e);
    }

    public void onBlur(Event e){
        System.out.println("===========觸發滑鼠失去焦點事件==========" + "\n" + e);
    }

    public void onFocus(Event e){
        System.out.println("===========觸發滑鼠獲得焦點事件==========" + "\n" + e);
    }

}

最後編寫客戶端測試程式碼。


    public static void main(String[] args) {
        EventListener listener = new MouseEventListener();

        Mouse mouse = new Mouse();
        mouse.addListener(MouseEventType.ON_CLICK,listener);
        mouse.addListener(MouseEventType.ON_MOVE,listener);

        mouse.click();
        mouse.move();
    }
		

關注微信公眾號『 Tom彈架構 』回覆“設計模式”可獲取完整原始碼。

【推薦】Tom彈架構:30個設計模式真實案例(附原始碼),挑戰年薪60W不是夢

本文為“Tom彈架構”原創,轉載請註明出處。技術在於分享,我分享我快樂!
如果本文對您有幫助,歡迎關注和點贊;如果您有任何建議也可留言評論或私信,您的支援是我堅持創作的動力。關注微信公眾號『 Tom彈架構 』可獲取更多技術乾貨!

相關文章