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。
  • 相关阅读:
    android 发送短信 怎样做到一条一条的发送,仅仅有在上一条发送成功之后才发送下一条短信
    qt学习笔记(五) QGraphicsPixmapItem与QGraphicsScene的编程实例 图标拖动渐变效果
    C小加 之 随机数
    垂死挣扎还是涅槃重生 -- Delphi XE5 公布会归来感想
    WIZnet推出串口转以太网模块WIZ550S2E
    java里,当long与上了int
    几个常见字符串处理函数的实现原理
    Android平台调用Web Service:演示样例
    怎样学好游戏编程
    void及void指针含义的深刻解析
  • 原文地址:https://www.cnblogs.com/syxchina/p/2199921.html
Copyright © 2011-2022 走看看