java观察者模式

为了说明为什么要用观察者模式以及使用观察者模式的好处,我们先来考虑如何用程序来模拟如下的场景。

本文借鉴尚学堂马士兵老师对观察者模式讲解整理而得。

1.小孩在睡觉
2.醒来后要求吃东西

第一种设计方式:

首先来分析需要建哪些类和方法,小孩(Child),醒来(wakeUp),大人(Father),喂食(feed)。

我们让Child是一个线程类,sleep一定时间就醒来。让大人也是一个线程类,不断的循环,看小孩是否醒来,醒来就喂食。

public class Child implements Runnable {
	private Boolean isWakeUp = false;

	public void run(){
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		wakeUp();

	}

	private void wakeUp() {
		isWakeUp = true;
		System.out.println("我醒了……要吃东西了……");
		
	}

	public Boolean getIsWakeUp() {
		return isWakeUp;
	}

}

public class Father implements Runnable{
	private Child child;
	
	public Father(Child child) {
		this.child = child;
	}
	public void run() {
		
		while(true){
			if(child.isWakeUp()){
				feed();
				break;
			}
			
		}
		
	}
	private void feed() {
		System.out.println("喂食了……");

	}
	
}

客户端

public class Test {
	public static void main(String[] args) {
		Child child = new Child();
		Father father = new Father(child);
		new Thread(child).start();
		new Thread(father).start();
		
	}
}

五秒过后就会打印如下信息

我醒了……要吃东西了……
喂食了……


第二种设计方式:

我们让child持有father的引用,child一醒就调用father的feed方法。为了我们的描述和观察者模式更为接近,我们对上面的类结构做一些改变。增加一个记录小孩醒来当前环境的类WakenUpEvent,如记录醒来时间,醒来地点,哪个小孩醒来。

class WakenUpEvent {
	private long time;//醒来时间
	private String loc;//醒来地点
	private Child source;//哪个小孩醒来
	
	public WakenUpEvent(long time, String loc, Child source) {
		super();
		this.time = time;
		this.loc = loc;
		this.source = source;
	}
	public long getTime() {
		return time;
	}
	public void setTime(long time) {
		this.time = time;
	}
	public String getLoc() {
		return loc;
	}
	public void setLoc(String loc) {
		this.loc = loc;
	}
	public Child getSource() {
		return source;
	}
	public void setSource(Child source) {
		this.source = source;
	}
}

增加一个观察者接口WakenUpListener,现在的情况是只要实现了WakenUpListener的人就可以给小孩喂食。将喂食方法feed 改为actionToWakenUp,表示现在孩子醒来我不单可以喂食还可以做其他操作,增加程序的可扩展性。

interface WakenUpListener {
	public void actionToWakenUp(WakenUpEvent wakenUpEvent);
}

Father类实现WakenUpListener接口,就不用线程了。

class Father implements WakenUpListener {

	public void ActionToWakenUp(WakenUpEvent wakenUpEvent) {
		System.out.println("feed child"+wakenUpEvent.getSource().getName());
	}
	
}


public class Child implements Runnable {
	
	private String name = "小红";
	private WakenUpListener listener ;

	public Child(WakenUpListener listener) {
		this.listener = listener;
	}

	public void run(){
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		wakeUp();

	}

	private void wakeUp() {
		System.out.println("我醒了……要吃东西了……");
		
		listener.ActionToWakenUp(
				new WakenUpEvent(System.currentTimeMillis(),"bed",this)
				);
		
	}

	
	public String getName() {
		return name;
	}

}

客户端

public class Test {
	public static void main(String[] args) {
		Child child = new Child(new Father());
		new Thread(child).start();
	}

}

5秒之后打印结果

我醒了……要吃东西了……
feed child小红


再来看第三种设计方式:

第三种设计方式我要让程序更具有扩展性。更容易的让多个观察者可以同时监听并处理小孩醒来的动作,不只是Father,而且根据不同的观察者作出不动的动作。child类让它持有监听者的一个引用的集合,也就是观察者注册。并且添加一个可以添加观察者的方法addWakenUpListener

class Child implements Runnable {
	private String name = "小红";
	private List<WakenUpListener> wakenUpListeners = new ArrayList<WakenUpListener>();

	public Child addWakenUpListener(WakenUpListener l) {
		wakenUpListeners.add(l);
		return this;
	}
	
	public String getName() {
		return name;
	}

	void wakeUp() {
		System.out.println("我醒了………");
		for(int i=0; i<wakenUpListeners.size(); i++) {
			WakenUpListener l = wakenUpListeners.get(i);
			l.ActionToWakenUp(new WakenUpEvent(System.currentTimeMillis(), "bed", this));
		}
	}

	public void run() {
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		this.wakeUp();
	}
	
}



下面两个类和第二种设计方式一样

interface WakenUpListener {
	public void ActionToWakenUp(WakenUpEvent wakenUpEvent);
}

class WakenUpEvent {
	private long time;//醒来时间
	private String loc;//醒来地点
	private Child source;//哪个小孩醒来
	
	public WakenUpEvent(long time, String loc, Child source) {
		super();
		this.time = time;
		this.loc = loc;
		this.source = source;
	}
	public long getTime() {
		return time;
	}
	public void setTime(long time) {
		this.time = time;
	}
	public String getLoc() {
		return loc;
	}
	public void setLoc(String loc) {
		this.loc = loc;
	}
	public Child getSource() {
		return source;
	}
	public void setSource(Child source) {
		this.source = source;
	}
}

下面是观察者类,也就是要注册到小孩持有的集合里的类,这些类可以同时对小孩醒来的事件做出不同的反应

class Father implements WakenUpListener {

	public void ActionToWakenUp(WakenUpEvent wakenUpEvent) {
		System.out.println("feed child"+wakenUpEvent.getSource().getName());
	}
	
}

class GrandFather  implements WakenUpListener {

	public void ActionToWakenUp(WakenUpEvent wakenUpEvent) {
		System.out.println("hug child");
	}
	
}

class Dog implements WakenUpListener {

	public void ActionToWakenUp(WakenUpEvent arg0) {
		System.out.println("wang wang ...");
	}
	
}

客户端

public class Test {
	public static void main(String[] args) {
		Child c = new Child();
		c.addWakenUpListener(new Dad()).addWakenUpListener(new GrandFather())
		.addWakenUpListener(new Dog());
		
		new Thread(c).start();
	}
}
5秒之后打印结果:

我醒了………
feed child小红
hug child
wang wang ...

到此我们针对本文开头的场景的三种设计方式就分析完了。对比我们发现,第二,第三种比第一种更灵活。而第三种我们用到了观察者模式,使程序更具扩展性,低耦合。可以不修改源码实现对小孩的任意监听处理,观察不难发现,我们如果将chid类提取出一个抽象类比如Observerable,让child去实现它。那们我们就可以让任何实现Observerable类的对象得到监听,这样我们就比较完整的使用了观察者模式。

下面我们再来分析一下jdk古老的AWT是怎么用观察者模式的。先回顾一下awt中button是怎么使用的。

import java.awt.Button;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

public class TestFrame extends Frame {
	public void launch() {
		Button b = new Button("press me");
		b.addActionListener(new MyActionListener());
		b.addActionListener(new MyActionListener2());
		this.add(b);
		this.pack();
		
		this.addWindowListener(new WindowAdapter(){

			@Override
			public void windowClosing(WindowEvent e) {
				System.exit(0);
			}
			
		});
		
		this.setVisible(true);
	}
	
	public static void main(String[] args) {
		new TestFrame().launch();
	}
	
	private class MyActionListener implements ActionListener {

		public void actionPerformed(ActionEvent e) {
			System.out.println("button pressed!");
		}
		
	}
	
	private class MyActionListener2 implements ActionListener {

		public void actionPerformed(ActionEvent e) {
			System.out.println("button pressed 2!");
		}
		
	}
}

我们再来用自己的方式模拟一下这个button


class Button {
	
	private List<ActionListener> actionListeners = new ArrayList<ActionListener>();
	
	public void buttonPressed() {
		ActionEvent e = new ActionEvent(System.currentTimeMillis(),this);
		for(int i=0; i<actionListeners.size(); i++) {
			ActionListener l = actionListeners.get(i);
			l.actionPerformed(e);
		}
	}
	
	public void addActionListener(ActionListener l) {
		actionListeners.add(l);
	}
}

interface ActionListener {
	public void actionPerformed(ActionEvent e);
}

class MyActionListener implements ActionListener {

	public void actionPerformed(ActionEvent e) {
		System.out.println("button pressed!");
	}
	
}

class MyActionListener2 implements ActionListener {

	public void actionPerformed(ActionEvent e) {
		System.out.println("button pressed 2!");
	}
	
}

class ActionEvent {
	
	long when;
	Object source;
	
	public ActionEvent(long when, Object source) {
		super();
		this.when = when;
		this.source = source;
	}
	
	
	public long getWhen() {
		return when;
	}

	public Object getSource() {
		return source;
	}
	
}

客户端

public class Test {
	public static void main(String[] args) {
		Button b = new Button();
		b.addActionListener(new MyActionListener());
		b.addActionListener(new MyActionListener2());
		b.buttonPressed();
	}
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值