设计模式(四) Observe——观察者模式

设计模式四 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();
    }
}

总结:
观察者模式的使用场景非常广泛,大多数用在对一些事件处理的场景。
观察者一般会有三个角色

被观察者、具体事件、观察者

  1. 在被观察者内部定义观察者集合,并在触发函数内部创建具体的事件对象,交给遍历中的观察者依次处理。
  2. 在具体事件内部关联具体事件属性及被观察者对象
  3. 观察者进行统一接口定义,并提供统一的执行方法,且该方法要设置具体的事件参数
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值