【原創】自己動手循序漸進實現觀察者模式
引言
自上一篇《自己動手實現牛逼的單例模式》問世之後,得到了不錯的評價。於是博主在五一放棄出去遊玩機會,趕制了這篇《自己動手循序漸進實現觀察者模式》,依然還是一步一步推導出最終版的觀察者模式。
觀察者模式的定義
在許多設計中,經常涉及多個對象都對一個特殊對象中的數據變化感興趣,而且這多個對象都希望跟蹤那個特殊對象中的數據變化,在這樣的情況下就可以使用觀察者模式。
在這裏,我們以母親觀察寶寶為例子,寶寶正在睡覺,醒來後,母親做出相應的餵食行為。
觀察者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類做如下幾點修改
- 持有Mother的引用,修改構造函數
- 在wakeUp方法中增加,通知母親的邏輯
- 在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類修改如下
- 刪除run方法等線程相關操作
- 增加一個無參構造方法(因為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都只是鋪墊,為了引出最後一版的觀察者模式,希望大家有所收獲。
【原創】自己動手循序漸進實現觀察者模式