观察者模式 Observer的定义

  观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。

  这个主题对象在状态上发生变化时,会通知所有观察者对象,让它们能够自动更新自己。

第一部分

这里有一个例子,是马士兵老师在讲解观察者模式的时候给出的例子,个人认为对理解观察者模式有很大的用处,自己查到的一些博文也写得很好,但是太过于一板一眼了,不便于去理解。具体的例子是这样的:一个小孩在睡觉,当小孩醒过来之后,爸爸要feed,爷爷要哄哄抱抱,小狗汪汪叫。在这里这个睡觉的小孩就是被观察的对象,后面三个对象就是观察者,小孩的状态发生改变的时候,就相当于一个事件被触发了,观察者(或者应该叫做监听者)会做出相应的动作。下面是具体的是代码实现。

第一步:我们定义被观察对象

class Child implements Runnable {
//用List来存放不同的监听
private List<WakeUpListener> wakeUpListeners = new ArrayList<WakeUpListener>(); //List中添加监听的操作
public void addWakenUpListener(WakeUpListener l) {
wakeUpListeners.add(l);
} //被观察者小孩的状态发生改变,则会通知观察者,使其执行各自的performAction方法
public void wakeUp() {
for (int i = 0; i < wakeUpListeners.size(); i++) {
WakeUpListener l = wakeUpListeners.get(i);
//
l.performAction(new WakeUpEvent(System.currentTimeMillis(), "沙发上",
this));
}
} //监听线程的run()
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.wakeUp();
} }

第二步:给出监听接口,具体的观察者都去实现这个接口,具体的观察者复写接口的performAction方法,小孩的状态发生变化,做出响应

 interface WakeUpListener {
public void performAction(WakeUpEvent wakeUpEvent);
}

第三步:定义具体的观察者

/*
* 具体观察者ConcreteObserver,被观察的对象child的状态发生变化这一事件会触发观察者的performAction方法,针对被观察者的变化做出反应
*/
//具体观察者一
class Dad implements WakeUpListener { public void performAction(WakeUpEvent wakeUpEvent) {
System.out.println("..feed..");
} }
//具体观察者二
class Dog implements WakeUpListener { public void performAction(WakeUpEvent wakeUpEvent) {
System.out.println("..汪汪..");
} }
//具体观察者三
class Grand implements WakeUpListener { public void performAction(WakeUpEvent wakeUpEvent) {
System.out.println("..hug..");
} }

第四步:定义事件类Event,Event事件类,将观察者状态的改变封装成Event类,不同的状态对应不同的事件。在我们的例子中,小孩的状态发生改变,他的观察者Dad、Dog、Grand会针对此事件做出反应;

class WakeUpEvent {
//描述了事件的一些基本的信息:时间+地点+被观察对象
private long time;
private String location;
private Child child; public WakeUpEvent(long time, String location, Child child) {
super();
this.time = time;
this.location = location;
this.child = child;
} 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 Child getChild() {
return child;
} public void setChild(Child child) {
this.child = child;
} }

第五步:下面的observers是我们的配置文件的文件名,尽量将这些动作的实现对客户端隐藏,用户不需要明白加载读取配合文件的操作,在做代码设计的时候要始终坚持这一原则。

try {
props.load(ObserveTest.class.getClassLoader().getResourceAsStream(
"Observers.properties"));
} catch (IOException e) {
e.printStackTrace();
}

我们在这里将读取配置文件的动作封装在类中:

//这里将读取配置文件Observers.properties的操作封装在类中,使用静态代码块的形式,在类加载的时候将配置文件加载进内存
//提高代码的灵活行,避免反复的执行加载配置文件的操作
class PropertyMgr {
// 重要的思想:缓存
// 单例初步以及缓存:把硬盘上的内容缓存到内存上
// 缓存的策略:访问最多的文件进行缓存
private static Properties props = new Properties();
// 这里使用了静态代码块,类加载的时候初始化一次
static {
try {
props.load(ObserveTest.class.getClassLoader().getResourceAsStream(
"Observers.properties"));
} catch (IOException e) {
e.printStackTrace();
}
}
//定义成静态static方法,方便在类外直接访问
public static String getProperty(String key) throws IOException {
return props.getProperty(key); }
}

最后一步:测试一下我们的程序,这里我给出完整的代码,方便读者的调试验证(这里附上我们的配置文件Observers.properties),只是一个简单的键值对应关系:observers=Grand,Dog,Dad

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties; import org.omg.CORBA.PRIVATE_MEMBER; //Event事件类,将观察者状态的改变封装成Event类,不同的状态对应不同的事件
//在我们的例子中,小孩的状态发生改变,他的观察者Dad、Dog、Grand会针对此事件做出反应
class WakeUpEvent {
//描述了事件的一些基本的信息:时间+地点+被观察对象
private long time;
private String location;
private Child child; public WakeUpEvent(long time, String location, Child child) {
super();
this.time = time;
this.location = location;
this.child = child;
} 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 Child getChild() {
return child;
} public void setChild(Child child) {
this.child = child;
} } //观察者模式中的Subject(目标),被观察对象 class Child implements Runnable {
//同List来存放不同的监听
private List<WakeUpListener> wakeUpListeners = new ArrayList<WakeUpListener>(); //List中添加监听的操作
public void addWakenUpListener(WakeUpListener l) {
wakeUpListeners.add(l);
} //被观察者小孩的状态发生改变,则会通知观察者,使其执行各自的performAction方法
public void wakeUp() {
for (int i = 0; i < wakeUpListeners.size(); i++) {
WakeUpListener l = wakeUpListeners.get(i);
//
l.performAction(new WakeUpEvent(System.currentTimeMillis(), "沙发上",
this));
}
} //监听线程的run()
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.wakeUp();
} }
/*
* 具体观察者ConcreteObserver,被观察的对象child的状态发生变化这一事件会触发观察者的performAction方法,针对被观察者的变化做出反应
*/
//具体观察者一
class Dad implements WakeUpListener { public void performAction(WakeUpEvent wakeUpEvent) {
System.out.println("..feed..");
} }
//具体观察者二
class Dog implements WakeUpListener { public void performAction(WakeUpEvent wakeUpEvent) {
System.out.println("..汪汪..");
} }
//具体观察者三
class Grand implements WakeUpListener { public void performAction(WakeUpEvent wakeUpEvent) {
System.out.println("..hug..");
} }
//抽象的观察Observer
interface WakeUpListener {
public void performAction(WakeUpEvent wakeUpEvent);
} public class ObserveTest { /**
* @param args
* @throws IOException
* @throws ClassNotFoundException
* @throws IllegalAccessException
* @throws InstantiationException
*/
public static void main(String[] args) throws Exception {
//读取配置文件的操作改成了静态方法,使用的时候直接调用,下面的observers是我们的配置文件的文件名
String observers[] = PropertyMgr.getProperty("observers").split(",");
Child child = new Child();
for (String s : observers) {
child.addWakenUpListener((WakeUpListener) Class.forName(s)
.newInstance());
}
new Thread(child).start();
}
} //这里将读取配置文件Observers.properties的操作封装在类中,使用静态代码块的形式,在类加载的时候将配置文件加载进内存
//提高代码的灵活行,避免反复的执行加载配置文件的操作
class PropertyMgr {
// 重要的思想:缓存
// 单例初步以及缓存:把硬盘上的内容缓存到内存上
// 缓存的策略:访问最多的文件进行缓存
private static Properties props = new Properties();
// 这里使用了静态代码块,类加载的时候初始化一次
static {
try {
props.load(ObserveTest.class.getClassLoader().getResourceAsStream(
"Observers.properties"));
} catch (IOException e) {
e.printStackTrace();
}
}
//定义成静态static方法,方便在类外直接访问
public static String getProperty(String key) throws IOException {
return props.getProperty(key); }
}

运行结果:

..hug..
..汪汪..
..feed..

第二部分

面试的过程可能会问到什么是观察者模式,其实这个时候不要给他们说什么太过于理论性的东西,举例子最方便不过了。观察者模式在java中的运用其实挺多的,比如说AWT和Swing中的监听机制用到的就是观察者模式,下面我们就来模拟一下看看监听机制是如何运作的。【注意】,代码中用到的类和方法都是我们自己定义的,不是调用API中的类和方法。

第一步:给出被监听对象:我们定义的一个按钮button

//首先定义一个按钮
class Button {
//创建一个具体的事件对象
ActionEvent e = new ActionEvent(System.currentTimeMillis(), this);
//List存储不同的监听者对象
private List<ActionListener> actionListeners = new ArrayList<ActionListener>(); //button按钮被按下时所触发的动作
public void buttonPressed() {
for (int i = 0; i < actionListeners.size(); i++) {
ActionListener l = actionListeners.get(i);
//按下button,监听者会做出相应的动作
l.actionPerformed(e);
}
} //add添加监听者的动作
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 pressed2");
}
}

第四步:定义监听事件Event,时间对象包括:时间的发生时间when+事件源

class ActionEvent {
long when;
Object source; public ActionEvent(long when, Object source) {
super();
this.when = when;
} 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();
}
}

运行结果:

button pressed
button pressed2

最后给出完整代码方便理解调试:

package com.observer.awt;

import java.util.ArrayList;

import java.util.List;

public class Test {
public static void main(String args[]) { Button b = new Button();
b.addActionListener(new MyActionListener());
b.addActionListener(new MyActionListener2());
b.buttonPressed();
}
} //首先定义一个按钮
class Button {
//创建一个具体的事件对象
ActionEvent e = new ActionEvent(System.currentTimeMillis(), this);
//List存储不同的监听者对象
private List<ActionListener> actionListeners = new ArrayList<ActionListener>(); //button按钮被按下时所触发的动作
public void buttonPressed() {
for (int i = 0; i < actionListeners.size(); i++) {
ActionListener l = actionListeners.get(i);
//按下button,监听者会做出相应的动作
l.actionPerformed(e);
}
} //add添加监听者的动作
public void addActionListener(ActionListener l) {
actionListeners.add(l);
}
} 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 pressed2");
}
} interface ActionListener {
public void actionPerformed(ActionEvent e);
} class ActionEvent {
long when;
Object source; public ActionEvent(long when, Object source) {
super();
this.when = when;
} public long getWhen() {
return when;
} public Object getSource() {
return source;
}
}

第三部分:我们在第二步给出了我们自己模拟的按钮按下触发相应动作的过程,第三部分给出AWT中,调用API中封装的一些已经实现好的类和方法,和第二步完成的是相同的动作。

package com.observer.awt;

import java.awt.Button;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter; public class TestFram extends Frame {
public void lanch() {
// 定义一个按钮,按钮显示的信息是"press me"
Button b = new Button("press me");
// 添加具体监听者
b.addActionListener(new MyActionListener());
b.addActionListener(new MyActionListener2());
// add方法将我们定义的button加入到Frame框架中
this.add(b);
// pack(),调整窗体的大小,里面可以添加参数
this.pack();
// 我们定义的TestFrame框架添加窗口监听
this.addWindowListener(new WindowAdapter() {
});
// 使窗体可见
this.setVisible(true);
} public static void main(String args[]) {
// 调用TestFram中的lanch方法,在Frame框架中定义一个按钮
new TestFram().lanch();
} // 具体的监听者
private class MyActionListener implements ActionListener { public void actionPerformed(ActionEvent e) {
// 监听者1观察到按钮被按下,做出反应
System.out.println("button pressed");
}
} private class MyActionListener2 implements ActionListener { public void actionPerformed(ActionEvent e) {
// 监听者2观察到按钮被按下,做出反应
System.out.println("button pressed 2!");
}
}
}

Java设计模式系列之观察者模式的更多相关文章

  1. Java 设计模式系列(十五)观察者模式(Observer)

    Java 设计模式系列(十五)观察者模式(Observer) Java 设计模式系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) Java ...

  2. Java设计模式之《观察者模式》及应用场景

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6513651.html 观察者模式,又可以称之为发布-订阅模式,观察者,顾名思义,就是一个监 ...

  3. Java设计模式系列-抽象工厂模式

    原创文章,转载请标注出处:https://www.cnblogs.com/V1haoge/p/10755412.html 一.概述 抽象工厂模式是对工厂方法模式的再升级,但是二者面对的场景稍显差别. ...

  4. Java设计模式系列-工厂方法模式

    原创文章,转载请标注出处:<Java设计模式系列-工厂方法模式> 一.概述 工厂,就是生产产品的地方. 在Java设计模式中使用工厂的概念,那就是生成对象的地方了. 本来直接就能创建的对象 ...

  5. Java设计模式系列-装饰器模式

    原创文章,转载请标注出处:<Java设计模式系列-装饰器模式> 一.概述 装饰器模式作用是针对目标方法进行增强,提供新的功能或者额外的功能. 不同于适配器模式和桥接模式,装饰器模式涉及的是 ...

  6. Java 设计模式系列(二二)责任链模式

    Java 设计模式系列(二二)责任链模式 责任链模式是一种对象的行为模式.在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链.请求在这个链上传递,直到链上的某一个对象决定处理此请求 ...

  7. Java 设计模式系列(二三)访问者模式(Vistor)

    Java 设计模式系列(二三)访问者模式(Vistor) 访问者模式是对象的行为模式.访问者模式的目的是封装一些施加于某种数据结构元素之上的操作.一旦这些操作需要修改的话,接受这个操作的数据结构则可以 ...

  8. Java 设计模式系列(十八)备忘录模式(Memento)

    Java 设计模式系列(十八)备忘录模式(Memento) 备忘录模式又叫做快照模式(Snapshot Pattern)或Token模式,是对象的行为模式.备忘录对象是一个用来存储另外一个对象内部状态 ...

  9. Java 设计模式系列(二十)状态模式

    Java 设计模式系列(二十)状态模式 状态模式,又称状态对象模式(Pattern of Objects for States),状态模式是对象的行为模式.状态模式允许一个对象在其内部状态改变的时候改 ...

随机推荐

  1. 公交wifi运营平台分析

    一.前言背景 昨晚下午,老板让看一些车载公交wifi后台管理的一些东西,这个随着移动端设备而兴起的wifi战,慢慢的也会越演越烈. 现在于很多人在外面的时候,进入一家店首先看的不是菜单,而是问一句“你 ...

  2. html5 touch事件实现触屏页面上下滑动(二)

    五一小长假哪都没去,睡了三天,今天晕晕沉沉的投入工作了,但还是做出了一点点效果 上周用html5的touch事件把简单的滑动做出来了,实现了持续页面上下滑动的效果,参考之前 的文章及代码html5 t ...

  3. OEM status|start|stop

    OEM一旦建立以后,LINUX的主机名(hosts)就不要去改变. [oracle@redhat4 ~]$ emctl start dbconsoleOC4J Configuration issue. ...

  4. Codeforces 383A - Milking cows

    原题地址:http://codeforces.com/problemset/problem/383/A 题目大意:有 n 头奶牛,全部看着左边或者右边,现在开始给奶牛挤奶,给一头奶牛挤奶时,所有能看到 ...

  5. UVa 1635 (唯一分解定理) Irrelevant Elements

    经过紫书的分析,已经将问题转化为求组合数C(n-1, 0)~C(n-1, n-1)中能够被m整除的个数,并输出编号(这n个数的编号从1开始) 首先将m分解质因数,然后记录下每个质因子对应的指数. 由组 ...

  6. 为 PHP 开发者准备的 12 个调试工具(转)

    为 PHP 开发者准备的 12 个调试工具 PHP是在实践中发展迅速并被最多使用的脚本语言:包含了诸如详细的文档.庞大的社区.无数可使用的脚本及支持框架等许多特性.PHP提供的这些特性使得它比Pyth ...

  7. 【转】./a.out 2>&1 > outfile

    原文网址:http://www.cnblogs.com/zhaoyl/archive/2012/10/22/2733418.html APUE 3.5关于重定向有个容易迷惑人的问题: ./a.out ...

  8. Python抓取单个网页中所有的PDF文档

    Github博文地址,此处更新可能不是很及时. 1.背景 最近发现算法以及数据结构落下了不少(其实还是大学没怎么好好学,囧rz),考虑到最近的项目结构越来越复杂了,用它来练练思路,就打算复习下数据结构 ...

  9. js控制不同的时间段显示不同的css样式

    js控制不同的时间段显示不同的css样式 js函数,可以放到单独的js文件中也可以放到当前页的<head>标记之内 function getCSS(){        datetoday ...

  10. 关于SecureCRT的安装和破解问题以及xp系统的串口问题

    今天下午找了几个小时的软件,因为交叉编译环境要搭好,其中SecureCRT还有串口问题要解决,我突然间发现我开始光盘中的SecureCRT坏掉了,在网站上下载SecureCRT,结果要很多的积分,这样 ...