为了说明为什么要用观察者模式以及使用观察者模式的好处,我们先来考虑如何用程序来模拟如下的场景。
本文借鉴尚学堂马士兵老师对观察者模式讲解整理而得。
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();
}
}