设计模式之Observer
这个模式可以参考awt包中的button极其相关事件的实现
说这个模式之前先用java简单的模拟一个场景吧(小孩在睡觉,醒了之后,爸爸要给他喂东西吃)。
先建两个类。爸爸这个类和小孩这个类,爸爸这个类主要提供一个负责向小孩喂东西的方法;小孩这个类主要提供一个哭的方法和一个什么时候哭的方法。
首先是爸爸这个类:Father.java
package com.yx.zzg.observer;public class Father {public void eatToChild(){System.out.println("eat to child....");}}然后是孩子这个类:
package com.yx.zzg.observer;public class Child implements Runnable{private Father f;private boolean isweakup = false;public Child(Father f) {super();this.f = f;}@Overridepublic void run() {try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}weekUp();}public boolean weekUp() {f.eatToChild();return isweakup = true;}}然后写一个测试的类:
package com.yx.zzg.observer;public class Test {public static void main(String[] args) {Child c = new Child(new Father());new Thread(c).start();}}写到这里我们这个场景就模仿完成了,可是我现在需要根据小孩醒的地点的不同,时间的不同,而这个小孩的爸爸所做出来的反应也不同,如半夜醒来的时候可以给她唱个摇篮曲,早上醒来的时候给他喂点粥喝。
一种办法是我们可以在孩子这个类里添加两个属性,然后根据属性来判断他爸爸应该做什么。
但是这样设计有两个不好的地方:
1.地点,时间这两个属性不应该属于孩子这个对象的。
2.孩子醒来的时间和地点不确定,也就是爸爸要做的事情也不明确,可能后来爸爸要做的事情会有很多种。
因此我们可以提取一个孩子哭的这样一个事件类。这个类包含三个属性,哭的时间,地点,以及事件源,时间源设为Object类,是因为现在是孩子醒这个时间,也可能是其他什么东西醒了。
package com.yx.zzg.observer;public class WeekUpEvent {private long time;private String location;private Object source;public WeekUpEvent(long time, String location, Object source) {super();this.time = time;this.location = location;this.source = source;}public long getTime() {return time;}public void setTime(long time) {this.time = time;}public String getLocation() {return location;}public void setLocation(String location) {this.location = location;}public Object getSource() {return source;}public void setSource(Child source) {this.source = source;}}因此这时我们可以修改我们的孩子类的weekUp方法
这个时候weekUp方法就是这个样子:
public boolean weekUp() {WeekUpEvent weekupEvent = new WeekUpEvent(System.currentTimeMillis(),"zhengzhou", this);if (weekupEvent.getLocation().equals("bj")) {f.eatToChild();}if (weekupEvent.getLocation().equals("hh")) {f.sing();}return isweakup = true;}但是这样做因为父亲要做的事情还是不明确,所以后来如果要添加方式的话我们还是要修改我们的weekUp方法,添加if else。这个时候我们应该怎么办呢?
因此这个时候我们应该抽取变的那部分内容,写一个接口
package com.yx.zzg.observer;public interface WeekUpListener {public void ActionWeekUp(WeekUpEvent weekUpEvent);}然后让爸爸那个类实现这个接口,这样爸爸那个类就可以根据事件的不同做出不同的反应。
package com.yx.zzg.observer;public class Father implements WeekUpListener {@Overridepublic void ActionWeekUp(WeekUpEvent weekUpEvent) {if (weekupEvent.getLocation().equals("bj")) {System.out.println("to eat....")}if (weekupEvent.getLocation().equals("hh")) {System.out.println("to sing.....")}}}这是我们可以修改我们的孩子类为:
package com.yx.zzg.observer;public class Child implements Runnable {WeekUpListener weekUpListener;public Child(WeekUpListener weekUpListener) {super();this.weekUpListener = weekUpListener;}@Overridepublic void run() {try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}weekUp();}public void weekUp() {weekUpListener.ActionWeekUp(new WeekUpEvent(System.currentTimeMillis(), "zhengzhou", this));}}因此我们的客户端就可以这样类写了
package com.yx.zzg.observer;public class Test {public static void main(String[] args) {WeekUpListener wul=new Father();Child c = new Child(wul);new Thread(c).start();}}因此这样做如果爸爸那个因素就具有扩展性了,比如如果爸爸那个类里面要根据时间不同提供其他的响应方式,我们就可以新建一个类,并且让这个类实现WeekUpListener 这个接口,在客户端就可以把WeekUpListener 的实现该为另外一个就可以了,其他的都不用任何改动。
但是这样做另外一个问题有出现了,这个时候我想妈妈也同时要做出反应,这个时候我们就可以这样做了,在孩子那个类里面放一个List,这个List用来接收所有的对孩子哭的这个事件的响应。应此孩子类最终就会成为这个样子:
package com.yx.zzg.observer;import java.util.ArrayList;import java.util.Iterator;import java.util.List;public class Child implements Runnable {private List<WeekUpListener> weekUpListeners = new ArrayList<WeekUpListener>();public void addWeekUpListener(WeekUpListener weekUpListener) {weekUpListeners.add(weekUpListener);}@Overridepublic void run() {try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}weekUp(new WeekUpEvent(System.currentTimeMillis(), "zhengzhou", this));}public void weekUp(WeekUpEvent weekUpEvent) {for (Iterator<WeekUpListener> iter = weekUpListeners.iterator(); iter.hasNext();) {WeekUpListener weekUpListener = iter.next();weekUpListener.ActionWeekUp(weekUpEvent);}}}而我们的测试类就会变成这个样子
package com.yx.zzg.observer;public class Test {public static void main(String[] args) {Child c=new Child();WeekUpListener wul = new Father();c.addWeekUpListener(wul);new Thread(c).start();}}如果我要想添加其他人对这个事件的响应,只需要忘Child添加一个对WeekUpListener的实现。
如果想跟灵活一写,我们可以把这些对WeekUpListener实现配置到一个属性文件里,这样如果有新的实现我们连Test类都不需要修改,只需该一下我们的配置文件
如:我们可以在我们项目的src目录下建一个observer.properties文件
内容为:
package com.yx.zzg.observer;import java.io.IOException;import java.util.Properties;public class PropertyMgr {private static Properties props = new Properties();static {try {//加载项目classpath路径下的properties文件props.load(Test.class.getClassLoader().getResourceAsStream("observer.properties"));} catch (IOException e) {e.printStackTrace();}}public static String getProperty(String key) {return props.getProperty(key);}}
最终测试类为:
package com.yx.zzg.observer;public class Test {public static void main(String[] args) {Child c = new Child();String[] str = PropertyMgr.getProperty("observers").split(",");for (String s : str) {try {c.addWeekUpListener((WeekUpListener) Class.forName(s).newInstance());} catch (InstantiationException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IllegalAccessException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (ClassNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();}}new Thread(c).start();}}