zoukankan      html  css  js  c++  java
  • 【原创】自己动手循序渐进实现观察者模式

    引言

    自上一篇《自己动手实现牛逼的单例模式》问世之后,得到了不错的评价。于是博主在五一放弃出去游玩机会,赶制了这篇《自己动手循序渐进实现观察者模式》,依然还是一步一步推导出最终版的观察者模式。

    观察者模式

    定义:在许多设计中,经常涉及多个对象都对一个特殊对象中的数据变化感兴趣,而且这多个对象都希望跟踪那个特殊对象中的数据变化,在这样的情况下就可以使用观察者模式。
    在这里,我们以母亲观察宝宝为例子,宝宝正在睡觉,醒来后,母亲做出相应的喂食行为。

    1、观察者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秒醒来
    已经给宝贝喂食
    

    2、观察者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秒醒来
    已经给宝贝喂食
    

    3、观察者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秒醒来
    给宝贝喂食

    ### 4、观察者V4
    这时情景变为下图
    <div align=center><img width="350" height="400" src="http://images.cnblogs.com/cnblogs_com/rjzheng/1194802/o_pic6.jpg"/></div>
    又过了几天,孩子不乐意了。。。。孩子觉得,我每次还要去记爸爸能带我干嘛,妈妈能带我干嘛,太麻烦了。以后,爷爷奶奶来来了,我难道还要去记你们能干嘛,再通知相应的人嘛。不如这样,我把你们全部叫来,你们自己商量好,该带我干嘛吧。
    
    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都只是铺垫,为了引出最后一版的观察者模式,希望大家有所收获。
  • 相关阅读:
    Java的参数传递是「按值传递」还是「按引用传递」?
    算法08 五大查找之:二叉排序树(BSTree)
    算法07 五大查找之:索引查找
    算法06 五大查找之:二分查找
    Django rest_framework实现RESTful API
    jenkins + pipeline构建自动化部署
    jenkins部署.net平台自动化构建
    python实现编写windows服务
    iis部署python运行环境
    Jenkins执行批处理文件、powershell失败
  • 原文地址:https://www.cnblogs.com/rjzheng/p/8971629.html
Copyright © 2011-2022 走看看