zoukankan      html  css  js  c++  java
  • 设计模式观察者模式(Observer Pattern with java)

    概述

    观察者模式(有时又被称为发布/订阅模式)是软体设计模式的一种。在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实作事件处理系统。

    先看一个例子

    有个小孩在睡觉,醒来之后要喂奶。

    我们使用的是java,所以不要闹出下面的笑话(披着面向对象的面向过程):

    public class Simulation {
    	public static void main(String... args) {
    		//小孩睡觉
    		//起来之后爸爸喂奶
    		//...
    	}
    }

    我们根据面向对象思想,加上多线程模拟Child和Dad,小孩在睡觉,随时可以起来,Dad隔一段时间看下小孩是否醒来。

    package observer;
    import java.util.Random;
    class Child implements Runnable {
    	public static Random r = new Random();
    	private boolean wake = false;
    	public Child() {
    		new Thread(this).start();
    	}
    	@Override
    	public void run() {
    		while (!wake) {
    			System.out.println("Child:I am sleeping...");
    			try {
    				Thread.sleep(1000);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			if (r.nextInt(10) > 8) {
    				wakeUp();
    			}
    		}
    	}
    	public void wakeUp() {
    		wake = true;
    	}
    	public boolean isWake() {
    		return wake;
    	}
    }
    
    class Dad implements Runnable {
    	private Child c;
    	public Dad(Child c) {
    		new Thread(this).start();
    		this.c = c;
    	}
    	@Override
    	public void run() {
    		while (!c.isWake()) {
    			System.out.println("Dad:child is sleeping...");
    			try {
    				Thread.sleep(1000);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    		feed(c);
    	}
    	private void feed(Child c2) {
    		System.out.println("feed child!");
    	}
    }
    
    public class FirstQuestion {
    	public static void main(String[] args) {
    		new Dad(new Child());
    	}
    }

    这样可以实现功能,但造成资源的浪费。开了这么多线程,Dad时间都用在看小孩身上了,Dad下午打牌的计划泡汤了。我们可以很容易的把Dad解放出来:

    class Child implements Runnable {
    	public static Random r = new Random();
    	private Dad d;
    	private boolean wake = false;
    	public Child(Dad d) {
    		this.d = d;
    	}
    	@Override
    	public void run() {
    		while (!wake) {
    			System.out.println("Child:I am sleeping...");
    			try {
    				Thread.sleep(1000);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			if (r.nextInt(10) > 8) {
    				wakeUp();
    			}
    		}
    	}
    	public void wakeUp() {
    		wake = true;
    		d.feed(this);
    	}
    	public boolean isWake() {
    		return wake;
    	}
    }
    class Dad {
    	public void feed(Child c) {
    		System.out.println("feed child!");
    	}
    
    }
    public class FirstQuestion {
    	public static void main(String[] args) {
    		new Thread(new Child(new Dad())).start();
    	}
    }

    这样基本已经实现功能,稍微完善下,假如想知道小孩什么时候起来等一些信息,如果我们写在小孩类中就不太合适,所以我们抽象出类中WakenUpEvent:

    class WakenUpEvent {
    	private Date date;
    	private String loc;
    	private Dad dad;
    	public WakenUpEvent(Date date, String loc, Dad dad) {
    		super();
    		this.date = date;
    		this.loc = loc;
    		this.dad = dad;
    	}
    }
    class Child implements Runnable {
    	private Dad d;
    	public Child(Dad d) {
    		this.d = d;
    	}
    	@Override
    	public void run() {
    		try {
    			Thread.sleep(5000);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		wakeUp();
    	}
    	public void wakeUp() {
    		d.actionToWakenUp(new WakenUpEvent(new Date(), "child", d));
    	}
    }
    
    class Dad {
    	public void actionToWakenUp(WakenUpEvent wakenUpEvent) {
    		System.out.println("child feed!");
    	}
    }
    public class FirstQuestion {
    	public static void main(String[] args) {
    		new Thread(new Child(new Dad())).start();
    	}
    }

    这样Dad可以做自己事,只要听到孩子声音,就过来喂奶。似乎问题已经解决,假如小孩醒后,小孩的爷爷想抱下,小孩家的小狗要叫下,等等,如果按照上面的设计,小孩需要持有爷爷GrandFather、狗Dog等的引用,再调用用响应的处理方法…需要修改较大的篇幅。实际上我们可以在小孩中使用一个集合存储所有监听小孩的对象,当小孩醒后,小孩依次调用监听者处理方法。要实现统一的接口,以可以被小孩监听器集合引用和调用相应方法,我们使用接口interface。

    class WakenUpEvent {
    	private Date date;
    	private String eventType;
    	private Object source;
    	public WakenUpEvent(Date date, String eventType, Object source) {
    		this.date = date;
    		this.eventType = eventType;
    		this.source = source;
    	}
    }
    
    class Child implements Runnable {
    	private List<WakenUpListener> list = new ArrayList<WakenUpListener>();
    	public void addWakenUpListener(WakenUpListener l) {
    		list.add(l);
    	}
    	@Override
    	public void run() {
    		System.out.println("child is sleeping...");
    		try {
    			Thread.sleep(2000);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		wakeUp();
    	}
    	public void wakeUp() {
    		for (WakenUpListener l : list) {
    			l.actionWakenUp(new WakenUpEvent(new Date(), "", this));
    		}
    	}
    }
    interface WakenUpListener {
    	void actionWakenUp(WakenUpEvent e);
    }
    class Dad implements WakenUpListener {
    	@Override
    	public void actionWakenUp(WakenUpEvent e) {
    		System.out.println("dad feed child!");
    	}
    }
    class GrandFather implements WakenUpListener {
    	@Override
    	public void actionWakenUp(WakenUpEvent e) {
    		System.out.println("grandfather holl child!");
    	}
    }
    public class FirstQuestion {
    	public static void main(String[] args) {
    		Child c = new Child();
    		c.addWakenUpListener(new Dad());
    		c.addWakenUpListener(new GrandFather());
    		new Thread(c).start();
    	}
    }

    这个时候再看概述的例子,比较容易理解了吧!

    定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。[GOF 《设计模式》]

    观察者模式中的推模式与拉模式[摘录]

    在Observer模式中区分推模式和拉模式,先简单的解释一下两者的区别:推模式是当有消息时,把消息信息以参数的形式传递(推)给所有观察者,而拉模式是当有消息时,通知消息的方法本身并不带任何的参数,是由观察者自己到主体对象那儿取回(拉)消息。知道了这一点,大家可能很容易发现上面我所举的例子其实是一种推模式的Observer模式。我们先看看这种模式带来了什么好处:当有消息时,所有的观察者都会直接得到全部的消息,并进行相应的处理程序,与主体对象没什么关系,两者之间的关系是一种松散耦合。但是它也有缺陷,第一是所有的观察者得到的消息是一样的,也许有些信息对某个观察者来说根本就用不上,也就是观察者不能“按需所取”;第二,当通知消息的参数有变化时,所有的观察者对象都要变化。鉴于以上问题,拉模式就应运而生了,它是由观察者自己主动去取消息,需要什么信息,就可以取什么,不会像推模式那样得到所有的消息参数。OK,说到这儿,你是否对于推模式和拉模式有了一点了解呢?

    实际上上面的代码中,因java中awt事件的影响,我在Event中加入了source字段,这算是拉模式的一种体现。我们可以得到公共的事件信息,也可以通过source得到发出事件对象的信息。

    AWT事件模拟

    说到AWT事件,我们根据上面的思路模拟下awt事件处理,观察者模式实现awt事件功能更加简单优雅,然而真正的awt也需要windows本身的事件驱动的支持,比如你按下某个button,首先windows捕获这个消息,把消息分发给java虚拟机,虚拟机在调用button相应的处理,button调用监听器处理(个人理解)。一般awt事件处理:

    public class AwtButton extends Frame {
    	public void lanch() {
    		Button b = new Button("test");
    		b.addActionListener(new MyActionListener());
    		b.addActionListener(new MyActionListener1());
    		this.add(b);
    		this.addWindowListener(new WindowAdapter() {
    			@Override
    			public void windowClosing(WindowEvent e) {
    				System.exit(0);
    			}
    		});
    		setSize(100, 100);
    		setVisible(true);
    	}
    	public static void main(String[] args) {
    		new AwtButton().lanch();
    	}
    	class MyActionListener implements ActionListener {
    		@Override
    		public void actionPerformed(ActionEvent e) {
    			System.out.println("button pressed!");
    		}
    	}
    	class MyActionListener1 implements ActionListener {
    		@Override
    		public void actionPerformed(ActionEvent e) {
    			System.out.println("button pressed1!");
    		}
    	}
    }

    结合上面的孩子的例子,我们使用控制台模拟awt事件:

    public class SimulationAwtButton {
    	public static void main(String[] args) {
    		Button b = new Button();
    		b.addActionListener(new MyActionListener1());
    		b.addActionListener(new MyActionListener2());
    		b.pressed();
    	}
    }
    class Button {
    	private List<ActionListener> list = new ArrayList<ActionListener>();
    	public void addActionListener(ActionListener l) {
    		list.add(l);
    	}
    	public void pressed() {
    		ActionEvent e = new ActionEvent(this);
    		for(ActionListener l : list) {
    			l.actionPerform(e);
    		}
    	}
    }
    interface ActionListener {
    	void actionPerform(ActionEvent e) ;
    }
    class ActionEvent {
    	private long time;
    	private Object source;
    	public ActionEvent(Object source) {
    		this.time = System.currentTimeMillis();
    		this.source = source;
    	}
    	public long getTime() {
    		return time;
    	}
    	public Object getSource() {
    		return source;
    	}
    }
    class MyActionListener1 implements ActionListener {
    	@Override
    	public void actionPerform(ActionEvent e) {
    		System.out.println("SimulationButton ActionPerformed:" +e.getTime() + e.getSource());
    	}
    }
    class MyActionListener2 implements ActionListener {
    	@Override
    	public void actionPerform(ActionEvent e) {
    		System.out.println("SimulationButton ActionPerformed:" +e.getTime() + e.getSource());
    	}
    }

    这样,对java送awt事件处理有了更深的认识。

    适用性

    1.当一个抽象模型有两个方面, 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。

    2.当对一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改变。

    3.当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不希望这些对象是紧密耦合的。

    总结

    通过Observer模式,把一对多对象之间的通知依赖关系的变得更为松散,大大地提高了程序的可维护性和可扩展性,也很好的符合了开放-封闭原则。

    参考资料

    .NET设计模式(19):观察者模式(Observer Pattern)(部分摘录原文)

    java尚学堂马士兵设计模式

    百度百科:观察者模式

    作者:BuildNewApp
    出处:http://syxchina.cnblogs.comBuildNewApp.com
    本文版权归作者、博客园和百度空间共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则作者会诅咒你的。
    如果您阅读了我的文章并觉得有价值请点击此处,谢谢您的肯定1。
  • 相关阅读:
    Atitit.播放系统规划新版本 v4 q18 and 最近版本回顾
    Atitit.播放系统规划新版本 v4 q18 and 最近版本回顾
    atitit.极光消息推送服务器端开发实现推送  jpush v3. 总结o7p
    atitit.极光消息推送服务器端开发实现推送  jpush v3. 总结o7p
    Atitit.文件搜索工具 attilax 总结
    Atitit.文件搜索工具 attilax 总结
    Atitit.软件命名空间  包的命名统计 及命名表(2000个名称) 方案java package
    Atitit.软件命名空间  包的命名统计 及命名表(2000个名称) 方案java package
    Atitit..状态机与词法分析  通用分词器 分词引擎的设计与实现 attilax总结
    Atitit..状态机与词法分析  通用分词器 分词引擎的设计与实现 attilax总结
  • 原文地址:https://www.cnblogs.com/syxchina/p/2199921.html
Copyright © 2011-2022 走看看