目录
观察者模式 简述
1、观察者模式又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。
2、观察者模式是类和类之间的关系,不涉及到继承。
3、观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
MySubject 类是主对象,Observer1(观察者1)和 Observer2 (观察者1)依赖于 MySubject 的对象,当 MySubject 变化时,Observer1 和 Observer2 必然变化。
AbstractSubject 类中定义着需要监控的对象列表,可以对其进行修改:增加或删除被监控对象,且当 MySubject 变化时,负责通知在列表内存在的对象
编码实现
1、下面使用代码实现上图,假如让一个人(Person)作为被观察对象,一只猫(Cat)和一只狗(Dog)作为观察者,当人开始吃饭时,猫和狗就会收到通知,知道它们的主人开始吃饭了。
2、观察者如下:
package com.lct.observerModel;
//观察者-设计成接口,所有的实现类都是观察者
public interface Observer {
/**
* 监控对象发生变化后,此方法用于收到通知,然后自己进行相应的处理
*
* @param message :接收的通知信息,如果需要接收发布者的信息,则可以加上,否则可以取消
*/
public void update(String message);
}
package com.lct.observerModel;
import java.util.logging.Logger;
//猫---实现 Observer 后,自己也是观察者
public class Cat implements Observer {
@Override
public void update(String message) {
Logger logger = Logger.getAnonymousLogger();
logger.info("汤姆猫:我看到主人准备吃 " + message + " ,我也开始吃猫粮...");
}
}
package com.lct.observerModel;
import java.util.logging.Logger;
//狗---实现 Observer 后,自己也是观察者
public class Dog implements Observer {
@Override
public void update(String message) {
Logger logger = Logger.getAnonymousLogger();
logger.info("哮天犬:我看到主人准备吃 " + message + " ,我也开始吃狗粮...");
}
}
3、被观察者/发布者如下:
package com.lct.observerModel;
//主题---设计成接口,制定规范
// Subject 与 Observer 是两条不同的线,通过 Subject(主题)添加、删除观察者、以及通知观察者等
public interface Subject {
public void addObserver(Observer observer);//添加观察者
public void delObserver(Observer observer);//剔除观察者
/**
* 通知所有观察者
*
* @param message :发送给观察者的信息,如果对方不需要接收信息,则可以取消
*/
public void notifyObservers(String message);
/**
* 上面是对观察者的操作,下面可以定义监控对象(主题)自身的操作
*
* @param food 食物名称
*/
public void eat(String food);
}
package com.lct.observerModel;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
/**
* 主题(被监控对象)抽象类,实现 主题接口
* 主要是显得层次更加清晰,所以采用 接口 -> 抽象类 -> 具体类 的结构
* 接口中定义所有规范,抽象类中实现所有对观察者的操作,主题具体的操作交由自己的子类去实现
*/
public abstract class AbstractSubject implements Subject {
private List<Observer> observerVector = new Vector<>();
@Override
public void addObserver(Observer observer) {
observerVector.add(observer);
}
@Override
public void delObserver(Observer observer) {
observerVector.remove(observer);
}
@Override
public void notifyObservers(String message) {
//通知所有观察者
Iterator<Observer> observerIterator = observerVector.iterator();
while (observerIterator.hasNext()) {
observerIterator.next().update(message);//通知的同时传递信息
}
}
}
package com.lct.observerModel;
import java.util.logging.Logger;
//主题具体的实现类,当然还可以有其它类型的子类,都实现自己 eat 方法即可
public class Person extends AbstractSubject {
@Override
public void eat(String food) {
Logger logger = Logger.getAnonymousLogger();
/**
* 在自己的执行之前通知所有观察者,则观察者先执行
* 同理放在自己的执行之后通知所有观察者,则观察者后执行
*/
notifyObservers(food);//将自己的吃的食物作为信息传递给观察者
logger.info("杨戬:本君要开始用膳 " + food);
}
}
4、测试如下:
package com.lct.test;
import com.lct.observerModel.*;
public class Test {
public static void main(String[] args) {
Observer observer_cat = new Cat();
Observer observer_dog = new Dog();
Subject subject = new Person();
subject.eat("蚂蚁上树");//此时未添加观察者,所以不会有对象收到通知
subject.addObserver(observer_cat);
subject.eat("辣椒炒肉");//此时 cat 会收到通知
subject.addObserver(observer_dog);
subject.eat("水煮鱼");//此时 cat 、dog 都会收到通知
}
}
番外版
1、这种方法相比上面的方式,比较不容易理解,但也是一种方法
2、观察者设计模式三要素:
事件源:触发事件的对象,需要注册监听器
监听器:一般设计成接口,由使用者来实现其中的它想要监听的方法
事件:触发的事件,里面要封装事件源
3、假设现在要监听学生(Student)小明的学习(study)和睡觉(sleep)情况
事件源
package test.publish;
/**
* Created by Administrator on 2018/7/20 0020.
* 事件源(观察的目标)----下面监听它的study()、 sleep()方法
* 需要注册监听器
*/
public class Student {
/**
* name:学生的普通属性
* bookName:学习的书名
* studentListener:学生监听器
*/
private String name;
private String bookName;
private StudentListener studentListener;
public void study(String bookName) {
this.bookName = bookName;
/** 当注册了监听器时,则实施方法监听
* 监听器方法中含有事件,事件又封装了事件源,而用户实现了监听器
* 所以每次此方法一运行,用户会先监听到,而且能从事件中获取事件源(Student)数据*/
if (studentListener != null) {
studentListener.studyListener(new StudentEven(this));
}
System.out.println("我是 " + name + " ,准备学习 " + bookName);
}
public void sleep() {
/** 当注册了监听器时,则实施方法监听,与上同理*/
if (studentListener != null) {
studentListener.sleepListener(new StudentEven(this));
}
System.out.println("我是 " + name + " ,准备睡觉!");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getBookName() {
return bookName;
}
/**
* 注册监听器
* 只要当注册了监听器,才能监听方法
*
* @param studentListener
*/
public void addStudentListener(StudentListener studentListener) {
this.studentListener = studentListener;
}
}
事件
package test.publish;
/**
* Created by Administrator on 2018/7/20 0020.
* 事件---封装事件源,便于事件源对象的传入传出
* 这样的好处是以后用户可以通过触发的事件获取事件源,从而可以获取数据源的数据
*/
public class StudentEven {
private Object evenSource;
public StudentEven(Object evenSource) {
this.evenSource = evenSource;
}
public Object getEvenSource() {
return evenSource;
}
public Student getStudent() {
return (Student) evenSource;
}
}
监听器
package test.publish;
/**
* Created by Administrator on 2018/7/20 0020.
* 监听学生的监听器,通常设计成接口,让用户自己实现
* 用户可以从 StudentEven 中获取事件源
*/
public interface StudentListener {
/**
* 监听器监听的方法
*
* @param studentEven 监听器监听的事件
*/
void studyListener(StudentEven studentEven);
void sleepListener(StudentEven studentEven);
}
用户监听
package test.publish;
/**
* Created by Administrator on 2018/7/20 0020.
* 自己实现监听器,这和使用Java Web的监听器道理是一样的
* 如果只想监听里面的部分方法,则可以再加适配器
*/
public class MyStudentListener implements StudentListener {
@Override
public void studyListener(StudentEven studentEven) {
Student student = studentEven.getStudent();
System.out.println("-----------监听到 " + student.getName() + " 开始学习 " + student.getBookName());
}
@Override
public void sleepListener(StudentEven studentEven) {
Student student = studentEven.getStudent();
System.out.println("-----------监听到 " + student.getName() + " 开始睡觉");
}
}
测试结果
/**
* Created by Administrator on 2018/7/20 0020.
* 未注册监听器时
*/
public class MyTest {
public static void main(String[] args) {
Student student = new Student();
student.setName("小明同学");
student.study("论语");
student.sleep();
}
}
//输出如下
//我是 小明同学 ,准备学习 论语
//我是 小明同学 ,准备睡觉!
//Process finished with exit code 0
/**
* Created by Administrator on 2018/7/20 0020.
* 注册监听后
*/
public class MyTest {
public static void main(String[] args) {
Student student = new Student();
student.setName("小明同学");
student.addStudentListener(new MyStudentListener());
student.study("论语");
student.sleep();
}
}
/*
-----------监听到 小明同学 开始学习 论语
我是 小明同学 ,准备学习 论语
-----------监听到 小明同学 开始睡觉
我是 小明同学 ,准备睡觉!
Process finished with exit code 0
*/