1. 程式人生 > >【原創】自己動手循序漸進實現觀察者模式

【原創】自己動手循序漸進實現觀察者模式

接口 定義 。。 推導 ole com package exce ++

引言

自上一篇《自己動手實現牛逼的單例模式》問世之後,得到了不錯的評價。於是博主在五一放棄出去遊玩機會,趕制了這篇《自己動手循序漸進實現觀察者模式》,依然還是一步一步推導出最終版的觀察者模式。

觀察者模式的定義

在許多設計中,經常涉及多個對象都對一個特殊對象中的數據變化感興趣,而且這多個對象都希望跟蹤那個特殊對象中的數據變化,在這樣的情況下就可以使用觀察者模式。
在這裏,我們以母親觀察寶寶為例子,寶寶正在睡覺,醒來後,母親做出相應的餵食行為。

觀察者V1

先定義一個Baby類,baby先睡五秒,五秒後醒來,代碼如下所示

package rjzheng.observer1;
public class Baby implements Runnable {
    // 默認是睡著
    private boolean wakeup = false;
    // 醒來的行為
    public void wakeUp() {
        this.wakeup = true;
    }
    public boolean isWakeup() {
        return wakeup;
    }
    public void setWakeup(boolean wakeup) {
        this.wakeup = wakeup;
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        wakeUp();
    }
}

而母親類即Mother類,則一直監聽寶寶的狀態,寶寶一醒來就餵食,代碼如下所示

package rjzheng.observer1;
public class Mother implements Runnable {
    private Baby baby;
    public Mother(Baby baby) {
        this.baby = baby;
    }
    public void feed(Baby baby){
        System.out.println("已經給寶貝餵食");
    }
    @Override
    public void run(){
        while(!baby.isWakeup()){
            for(int i=0;i<5;i++){
                try {
                    Thread.sleep(1000);
                    System.out.println("寶寶還有"+(5-i)+"秒醒來");
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
        this.feed(baby);
    }
}

然後寫一個ObserverTest1測試一下,代碼如下所示

package rjzheng.observer1;

public class ObserverTest1 {
    public static void main(String[] args) {
        Baby baby =new Baby();
        new Thread(baby).start();
        new Thread(new Mother(baby)).start();
        
    }
}

輸出如下

寶寶還有5秒醒來
寶寶還有4秒醒來
寶寶還有3秒醒來
寶寶還有2秒醒來
寶寶還有1秒醒來
已經給寶貝餵食

觀察者V2

可是上一版,母親要一直看著寶寶,母親覺得太累了,母親要是想中間聊個微信啥的,多不方便,於是V2版出現了。我們能不能孩子醒來後,主動告訴給母親呢。於是修改Baby的wakeUp方法,即醒來狀態改變後,增加一個通知母親的方法。
綜上所述,Baby類做如下幾點修改

  1. 持有Mother的引用,修改構造函數
  2. 在wakeUp方法中增加,通知母親的邏輯
  3. 在run方法中,自己五秒後醒來,自己修改自己的狀態
package rjzheng.observer2;
public class Baby implements Runnable {
    // 默認是睡著
    private boolean wakeup = false;
    
    private Mother mother;
    //1. 持有Mother的引用,修改構造函數
    public Baby(Mother mother){
        this.mother = mother;
    }
    
    //2. 在wakeUp方法中增加,通知母親的邏輯
    public void wakeUp() {
        wakeup = true;
        this.mother.feed(this);
    }
    public boolean isWakeup() {
        return wakeup;
    }
    public void setWakeup(boolean wakeup) {
        this.wakeup = wakeup;
    }
    //3. 在run方法中,自己五秒後醒來,自己修改自己的狀態
    @Override
    public void run() {
        while(!this.isWakeup()){
            for(int i=0;i<5;i++){
                try {
                    Thread.sleep(1000);
                    System.out.println("寶寶還有"+(5-i)+"秒醒來");
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            this.wakeUp();
        }
    }
}

而Mother類,則不需要做線程操作,Baby類通知Mother類後,做出相應處理即可。因此,Mother類修改如下

  1. 刪除run方法等線程相關操作
  2. 增加一個無參構造方法(因為Mother類不需要持有Baby類的引用了)
    綜上所述,Mother類的代碼如下
package rjzheng.observer2;
public class Mother{
    private Baby baby;
    public Mother(){
        
    }
    public Mother(Baby baby) {
        this.baby = baby;
    }
    public void feed(Baby baby){
        System.out.println("已經給寶貝餵食");
    }
}

測試類ObserverTest2的代碼如下

package rjzheng.observer2;

public class ObserverTest2 {
    public static void main(String[] args) {
        Baby baby =new Baby(new Mother());
        new Thread(baby).start();
        
    }
}

輸出如下所示

寶寶還有5秒醒來
寶寶還有4秒醒來
寶寶還有3秒醒來
寶寶還有2秒醒來
寶寶還有1秒醒來
已經給寶貝餵食

觀察者V3

這個時候,寶寶的父親回來了。父親覺得,在寶寶醒來後,應該帶寶寶出去玩。於是就和孩子他媽約定,根據孩子醒來的時間決定,帶孩子做什麽。如果醒來的時間是飯點,就給孩子餵食;如果醒來的時間不是飯點,就帶孩子出去玩。
那麽重點來了,醒來的時間是否為飯點這個屬性,放在父親類裏,不大合適;放在寶寶類裏也不大合適;放母親類裏也不大合適。根據面向對象的設計原則,我們定義一個醒來的事件類WakeUpEvent,將醒來的時間是否為飯點放在WakeUpEvent裏。除此之外,再定義一個事件源source屬性,那麽WakeUpEvent類如下所示

package rjzheng.observer3;
/**
 * 醒來的事件對象
 * @author zhengrongjun
 *
 */
public class WakeUpEvent {
    //醒來時間是否為飯點
    private boolean isFoodTime;
    //事件源
    private Baby source;
    public WakeUpEvent(boolean isFoodTime,Baby source){
        this.isFoodTime = isFoodTime;
        this.source = source;
    }
    public boolean isFoodTime() {
        return isFoodTime;
    }
    public void setFoodTime(boolean isFoodTime) {
        this.isFoodTime = isFoodTime;
    }
    public Baby getSource() {
        return source;
    }
    public void setSource(Baby source) {
        this.source = source;
    }
}

那麽,接下來,Father類擁有一個帶寶貝出去玩的方法,如下所示

package rjzheng.observer3;

public class Father {
    
    public void play(WakeUpEvent wakeUpEvent){
        if(!wakeUpEvent.isFoodTime()){
            System.out.println("抱寶貝出去玩");
        }
    }
}

自然,Mother類擁有一個給寶貝餵食的方法,如下所示

package rjzheng.observer3;
public class Mother{
    public void feed(WakeUpEvent wakeUpEvent){
        if(wakeUpEvent.isFoodTime()){
            System.out.println("給寶貝餵食");
        }
    }
}

那麽在這種情況下Baby類修改為如下所示

package rjzheng.observer3;

public class Baby implements Runnable {

    private Mother mother;
    private Father father;

    public Baby(Mother mother,Father father) {
        this.mother = mother;
        this.father = father;
    }

    public void wakeUp() {
        this.mother.feed(new WakeUpEvent(true, this));
        this.father.play(new WakeUpEvent(true, this));
    }

    @Override
    public void run() {
        boolean flag = true;
        while (flag) {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                    System.out.println("寶寶還有" + (5 - i) + "秒醒來");
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            this.wakeUp();
            flag=false;
        }
    }
}

測試類ObserverTest3如下所示

package rjzheng.observer3;

public class ObserverTest3 {
    public static void main(String[] args) {
        Baby baby =new Baby(new Mother(),new Father());
        new Thread(baby).start();
        
    }
}

運行結果如下

寶寶還有5秒醒來
寶寶還有4秒醒來
寶寶還有3秒醒來
寶寶還有2秒醒來
寶寶還有1秒醒來
給寶貝餵食

觀察者V4

又過了幾天,孩子不樂意了。。。。孩子覺得,我每次還要去記爸爸能帶我幹嘛,媽媽能帶我幹嘛,太麻煩了。以後,爺爺奶奶來來了,我難道還要去記你們能幹嘛,再通知相應的人嘛。不如這樣,我把你們全部叫來,你們自己商量好,該帶我幹嘛吧。

OK。。這也是觀察者V3版的缺點,代碼復用性太差。如果多一個爺爺GrandFather類,那Baby類裏頭還要多一個Grandfather類的引用,並且在wakeup裏增加通知邏輯,太麻煩。索性,定義一個WakeUpListener接口,繼承這個接口的人,代表都具有處理WakeUpEvent事件的能力

那麽WakeUpListener接口的源碼如下

package rjzheng.observer4;

public interface WakeUpListener {
    public void actiontoWakenUp(WakeUpEvent wakeUpEvent);
}

這時Father類和Mother類代碼如下,都實現了WakeUpListener接口

package rjzheng.observer4;

public class Father implements WakeUpListener {

    @Override
    public void actiontoWakenUp(WakeUpEvent wakeUpEvent) {
        // TODO Auto-generated method stub
        if(!wakeUpEvent.isFoodTime()){
            System.out.println("抱寶貝出去玩");
        } 
    }
}
package rjzheng.observer4;
public class Mother implements WakeUpListener{

    @Override
    public void actiontoWakenUp(WakeUpEvent wakeUpEvent) {
        if(wakeUpEvent.isFoodTime()){
            System.out.println("給寶貝餵食");
        }
    }
}

這時的Baby類如下所示

package rjzheng.observer4;

import java.util.ArrayList;
import java.util.List;

public class Baby implements Runnable {

    private List<WakeUpListener> wakeUpListeners = new ArrayList(); 

    public void addListeners(WakeUpListener wakeUpListener){
        this.wakeUpListeners.add(wakeUpListener);
    }

    public void wakeUp() {
        for(WakeUpListener listener : wakeUpListeners)
            listener.actiontoWakenUp(new WakeUpEvent(true, this));
    }

    @Override
    public void run() {
        boolean flag = true;
        while (flag) {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                    System.out.println("寶寶還有" + (5 - i) + "秒醒來");
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            this.wakeUp();
            flag=false;
        }
    }
}

測試類ObserverTest4為

package rjzheng.observer4;

public class ObserverTest4 {
    public static void main(String[] args) {
        Baby baby =new Baby();
        baby.addListeners(new Father());
        baby.addListeners(new Mother());
        new Thread(baby).start();
    }
}

最後結果如下

寶寶還有5秒醒來
寶寶還有4秒醒來
寶寶還有3秒醒來
寶寶還有2秒醒來
寶寶還有1秒醒來
給寶貝餵食

總結

本篇文章給出了四種觀察者模式,一般都是用V4版啦。。前面V1,V2,V3都只是鋪墊,為了引出最後一版的觀察者模式,希望大家有所收獲。

【原創】自己動手循序漸進實現觀察者模式