设计模式四 Observe——观察者模式
观察者模式
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。
在实际中经常提到的
- Observe(观察者)
- Listener(监听)
- Hook(钩子)
- Callback(回调)
其实都可以算是观察者模式
观察者模式有些类似于JavaScript中的事件机制:
JS中的事件中有三个部分构成:
事件源、事件、事件处理方式
观察者模式中也可以划分三个元素:
被观察者、触发事件、观察者
情景描述:
当水烧开时,操作者会处理后续事件,例如:爸爸会煮饺子,妈妈会下面条,小狗会对着滚水汪汪叫等
分析:
如果不使用任何设计模式及java思想,那么就需要写一个死循环并一直等待水是否烧开(用其他线程来控制烧水状态)
public static void main(String[] args){
//代表水的状态 false为水没有烧开 true代表水开了
boolean hot = false;
while(!hot){
//等待烧水
}
//烧开后处理内容
}
这样的方式实现很不友好,甚至都没有面向对象的思想,如果变成面向对象的思想,那么可以将“水”封装成一个类
package com.liqk.dp.observe;
public class ObserveMain {
/**
*虽然加入面向对象的方法
* 但仍处于傻等的状态
* 此处小例子,不再考虑线程同步问题
*/
public static void main(String[] args) {
Water water = new Water();
while(!water.isHot()){
try {
//每一秒看下水的状态
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//继续等待...
System.out.println("observe...");
}
//直到其他线程执行boil方法 才能在此做后续处理
}
}
/**将水抽象成一个类
* 水是否烧开设置为水的属性
* 添加查看水状态的方法
* 添加水烧开的方法
* */
class Water{
private boolean hot;
//获取水的状态
public boolean isHot(){
return hot;
}
//水烧开了
public void boil(){
System.out.println("水烧开了 呜呜呜~~~~");
hot = true;
}
}
优点:加入面向对象的思想
缺点:操作者一直盯着水傻等(while一直循环,直到改变水的状态)
那么就希望改进为,如果水开了boil()
被调用时,直接告知操作者来进行后续操作。那么此时引入观察者爸爸(Father)、妈妈(Mon)、小狗(Dog)
package com.liqk.dp.observe;
public class ObserveMain {
/**
*加入观察者
*/
public static void main(String[] args) {
Water water = new Water();
//do something
water.boil();
}
}
/**
* 在被观察对象中添加观察者
* 这样可以在事件触发时直接调用观察者的方法
* */
class Water{
private boolean hot;
//加入观察者对象
private Father father = new Father();
private Mon mon = new Mon();
private Dog dog = new Dog();
//获取水的状态
public boolean isHot(){
return hot;
}
//水烧开了
public void boil(){
System.out.println("水烧开了 呜呜呜~~~~");
hot = true;
//当boil被调用时 可以直接告知观察者 开始执行后续方法
father.cookDumpling();
mon.cookNoodles();
dog.wang();
}
}
/**观察者--爸爸*/
class Father{
public void cookDumpling(){
System.out.println("爸爸开始煮饺子");
}
}
/**观察者--妈妈*/
class Mon{
public void cookNoodles(){
System.out.println("妈妈开始下面条");
}
}
/**观察者--小狗*/
class Dog{
public void wang(){
System.out.println("小狗开始汪汪叫");
}
}
这种模式将观察者直接加入被观察对象
优点:
不需要傻等,只需要事件被触发(boil()
被调用),就可以直接通知相关观察者执行相应方法
缺点:
观察者的数量和每个观察者的处理方式可能都不相同,所以此时如果添加新的观察者时,改动较大。观察者和被观察者的耦合性太高。
针对以上问题,可以封装一个观察者统一接口,之后让每个具体的观察者来实现该接口。这样就可以借鉴责任链模式对所有的观察者进行统一管理。
package com.liqk.dp.observe;
import java.util.ArrayList;
import java.util.List;
public class ObserveMain {
public static void main(String[] args) {
Water water = new Water();
//向被观察者添加观察者对象
water.addObserve(new Father());
water.addObserve(new Mon());
water.addObserve(new Dog());
//do something
water.boil();
}
}
/**
*分离观察者与被观察者 解耦
*/
class Water{
private boolean hot;
//注册观察者集合
List<Boserve> boserves = new ArrayList<>();
//提供添加观察者方法
public void addObserve(Observe observe){
observes.add(observe);
}
public boolean isHot(){
return hot;
}
public void boil(){
System.out.println("水烧开了 呜呜呜~~~~");
hot = true;
//依次通知观察者执行相应方法
for (Boserve b: boserves) {
//参考责任链模式
// 还可以在每个观察者中处理相应操作,当执行到某个观察者时,可以决定是否继续执行(此处未实现)
b.actionOnBoil();
}
}
}
/**统一观察者接口,并提供统一的执行方法*/
interface Boserve{
void actionOnBoil();
}
/**观察者--爸爸*/
class Father implements Boserve{
public void cookDumpling(){
System.out.println("爸爸开始煮饺子");
}
@Override
public void actionOnBoil() {
cookDumpling();
}
}
/**观察者--妈妈*/
class Mon implements Boserve{
public void cookNoodles(){
System.out.println("妈妈开始下面条");
}
@Override
public void actionOnBoil() {
cookNoodles();
}
}
/**观察者--小狗*/
class Dog implements Boserve{
public void wang(){
System.out.println("小狗开始汪汪叫");
}
@Override
public void actionOnBoil() {
wang();
}
}
此时可以对观察者进行任意添加,并参照责任链模式进行执行或中断。
新问题:
当前观察者只能通过水开这个方法boil()
来执行固定的后续内容,无法获知当前是什么对象以什么状态通知观察者的。
例如:当前观察者只能知道水开了就开始下面条、煮饺子等操作。
但无法得知当前水是用什么容器烧开的(在小加热杯烧开,不方便操作),水中是否有脏东西等问题(又脏东西不执行任何操作),所以观察者无法获知被观察者的状态从而执行相应的操作。
解决方式:
将被观察者的事件单独封装成一个类,在该类中描述一些事件的状态,以及事件源(被观察者)是谁。
且事件也可以构成事件体系,由一个基类事件,派生出很多的事件,构成一个体系。
package com.liqk.dp.observe;
import jdk.nashorn.internal.ir.GetSplitState;
import java.util.ArrayList;
import java.util.List;
/**
* 有很多时候,观察者需要根据事件的具体情况来进行处理
* 大多数时候,我们处理事件的时候,需要事件源对象
* 事件也可以形成继承体系
* */
public class ObserveMain {
public static void main(String[] args) {
Water water = new Water();
//向被观察者添加观察者对象
water.addObserve(new Father());
water.addObserve(new Mon());
water.addObserve(new Dog());
//do something
water.boil();
}
}
class Water{
private boolean hot;
//注册观察者集合
List<Observe> observes = new ArrayList<>();
//提供添加观察者方法
public void addObserve(Observe observe){
observes.add(observe);
}
//获取水的状态
public boolean isHot(){
return hot;
}
//水烧开了
public void boil(){
System.out.println("水烧开了 呜呜呜~~~~");
hot = true;
//此处创建一个具体的事件,之后连同事件源自身传递给观察者遍历处理
WaterEvent waterEvent = new WaterEvent("铁锅",true,this);
//依次通知观察者执行相应方法
for (Observe b: observes) {
b.actionOnBoil(waterEvent);
}
}
}
/**
* 封装事件类顶层
* 事件也可以形成体系--事件可以依次继承 之后形成一套事件体系
* */
abstract class Event<T>{
public abstract T getSource();
}
/**
* 水事件类继承事件基类
* 他们就是一个简单的事件体系
* */
class WaterEvent extends Event<Water>{
String container;//容器
boolean state;//是否干净
Water source;//定义事件源
public WaterEvent(String container, boolean state ,Water source) {
this.container = container;
this.state = state;
this.source = source;
}
//此时该类中就可以通过getSource()方法来获取到事件源
@Override
public Water getSource() {
return source;
}
}
/**统一观察者接口,并提供统一的执行方法*/
interface Observe{
//通过将事件单独封装,此时就可以定制具体的事件并传递给观察者
void actionOnBoil(WaterEvent waterEvent);
}
/**观察者--爸爸*/
class Father implements Observe{
public void cookDumpling(){
System.out.println("爸爸开始煮饺子");
}
@Override
public void actionOnBoil(WaterEvent waterEvent) {
//此处就可以获取当前事件的具体特性,并根据需求加以处理
System.out.println("事件源:"+waterEvent.getSource());
System.out.println("烧水容器:"+waterEvent.container);
System.out.println("水中杂质:"+waterEvent.state);
//为省事,此处不做逻辑处理,直接执行
cookDumpling();
}
}
/**观察者--妈妈*/
class Mon implements Observe{
public void cookNoodles(){
System.out.println("妈妈开始下面条");
}
@Override
public void actionOnBoil(WaterEvent waterEvent) {
cookNoodles();
}
}
/**观察者--小狗*/
class Dog implements Observe{
public void wang(){
System.out.println("小狗开始汪汪叫");
}
@Override
public void actionOnBoil(WaterEvent waterEvent) {
wang();
}
}
总结:
观察者模式的使用场景非常广泛,大多数用在对一些事件处理的场景。
观察者一般会有三个角色
被观察者、具体事件、观察者
- 在被观察者内部定义观察者集合,并在触发函数内部创建具体的事件对象,交给遍历中的观察者依次处理。
- 在具体事件内部关联具体事件属性及被观察者对象
- 观察者进行统一接口定义,并提供统一的执行方法,且该方法要设置具体的事件参数