设计模式系列 — 观察者模式

点赞再看,养成习惯,公众号搜一搜【一角钱技术】关注更多原创技术文章。
本文 GitHub org_hejianhui/JavaStudy 已收录,有我的系列文章。

前言

23种设计模式快速记忆的请看上面第一篇,本篇和大家一起来学习观察者模式相关内容。

模式定义

定义了对象之间的一对多依赖,让多个观察者对象同时监听某一个主题对象,当主题对象发生变化时,它的所有依赖者都会收到通知并更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。

观察者模式是对象之间一对多的一种模式,被依赖的对象是Subject,依赖的对象是Observer,Subject通知Observer变化,Subject为1,Observer为多。

解决的问题

一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

模式组成

组成(角色)作用
抽象主题角色(Subject)把所有对观察者对象的引用保存在一个集合中,每个抽象主题角色都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者角色。一般用一个抽象类和接口来实现。
抽象观察者角色(Observer)为所有具体的观察者定义一个接口,在得到主题的通知时更新自己。
具体主题角色(Subject1)在具体主题内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个子类实现。
具体观察者角色(Observer1)该角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。通常用一个子类实现。如果需要,具体观察者角色可以保存一个指向具体主题角色的引用。

实例说明

实例概况

某天下午班主任通知某班学生和老师将要听一节课,以此来对老师的授课质量进行评分。

  • 老师和学生收到后开始安排相关的课程;
  • 上课期间老师和班主任通过观察学生的神情来预判课程的讲的好坏
    • 老师观察到学生皱眉头可以适当调节课程气氛
    • 班主任观察到学生课堂氛围好转,给与高分
  • 课程结束后,班主任和老师回到办公室,一起回顾这节开心的课程。

使用步骤

步骤1:构建一个课程实体类

class Course {

    // 上课时间:time
    private Date time;
    // 上课地点:place
    private String place;
    // 上课内容:content
    private String content;

    // 省略get/set...

    public Course() {
    }

    public Course(Date time, String place, String content) {
        this.time = time;
        this.place = place;
        this.content = content;
    }
}

步骤2:构建一个发现者的抽象类以及相关的实现类,老师和班主任都分别继承了该接口并重写相关方法

abstract class Observer {
    abstract void update(Object args);

    public Observer(String identity) {
        this.identity = identity;
    }

    private String identity;

    public String getIdentity() {
        return identity;
    }

    public void setIdentity(String identity) {
        this.identity = identity;
    }
}

步骤3:创建一个具体的观察者角色,老师拿着教材开始来上课

/**
 * 老师类
 * - 观察者之一
 * - 观察学生的上课情况
 */
class TeacherObserver extends Observer {

    private Course course;

    @Override
    public void update(Object args) {
        DateFormat df = DateFormat.getTimeInstance(DateFormat.LONG, Locale.CHINA);
        System.out.println("我是王老师,正在讲课中...");
        
        course = new Course(new Date(), "A栋教学楼", "高等数学");
        System.out.println("今天上课时间:" + df.format(course.getTime()) + " 地点:" + course.getPlace() + " 上课内容:" + course.getContent());
    }

    public TeacherObserver(String identity) {
        super(identity);
    }
}

步骤4:创建一个具体的观察者角色,班主任来听课

/**
 * 班主任来听课
 * - 观察者之一
 * - 观察学生的上课情况
 */
class HeadTeacherObserver extends Observer {
    @Override
    public void update(Object args) {
        System.out.println("我是班主任来听课了,正在检查课程质量...");
        System.out.println("学生反馈课程质量为:" + args);
    }

    public HeadTeacherObserver(String identity) {
        super(identity);
    }
}

步骤5:创建被观察者抽象主题角色

/**
 * 主体类
 * - 模拟被观察者主体
 */
abstract class Subject {
    /**
     * 修改通知
     */
    abstract void doNotify();

    /**
     * 添加被观察者
     */
    abstract void addObservable(Observer o);

    /**
     * 移除被观察者
     */
    abstract void removeObservable(Observer o);
}

步骤6:创建具体的被观察者主体角色,学生主体为被观察对象

/**
 * 学生主体
 * - 被观察的对象
 */
class StudentSubject extends Subject {
    /**
     * 上课状态
     */
    private String state;
	
    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }
    private List<Observer> observableList = new ArrayList<>();

    @Override
    public void doNotify() {
        for (Observer observer : observableList) {
            observer.update(state);
        }
    }

    @Override
    public void addObservable(Observer observable) {
        observableList.add(observable);
    }

    @Override
    public void removeObservable(Observer observable) {
        try {
            if (observable == null) {
                throw new Exception("要移除的被观察者不能为空");
            } else {
                if (observableList.contains(observable) ) {
                    System.out.println("下课了,"+observable.getIdentity()+" 已回到办公室");
                    observableList.remove(observable);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

步骤7:开始上课,开始记录报告

/**
 * 观察者模式
 */
public class ObserverPattern {

    public static void main(String[] args) {
        // 创建学生主体
        StudentSubject studentSubject = new StudentSubject();

        // 创建观察者老师
        TeacherObserver teacherObversable = new TeacherObserver("王老师");

        // 创建观察者班主任
        HeadTeacherObserver headTeacherObserver = new HeadTeacherObserver("班主任");

        // 学生反映上课状态
        studentSubject.setState("讲的不错,很好!");
        studentSubject.addObservable(teacherObversable);
        studentSubject.addObservable(headTeacherObserver);

        // 开始上课
        studentSubject.doNotify();

        // 上课结束
        studentSubject.removeObservable(headTeacherObserver);
        studentSubject.removeObservable(teacherObversable);
    }
}

输出结果

我是王老师,正在讲课中...
今天上课时间:下午115701秒 地点:A栋教学楼 上课内容:高等数学
我是班主任来听课了,正在检查课程质量...
学生反馈课程质量为:讲的不错,很好!
下课了,班主任 已回到办公室
下课了,王老师 已回到办公室

优点

  • 符合开闭原则
  • 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系;
  • 目标与观察者之间建立了一套触发机制。

缺点

  • 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用;
  • 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。

应用场景

当更改一个对象的状态可能需要更改其他对象,并且实际的对象集事先未知或动态更改时,请使用观察者模式。

注意事项: 1、JAVA 中已经有了对观察者模式的支持类。 2、避免循环引用。 3、如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。

源码中的应用

#JDK:
java.util.Observable

#Spring:
org.springframework.context.ApplicationListener

Observable源码分析

Observable接口
public interface Observer {
	void update(Observable o, Object arg);
}
Observable类
public class Observable {
	private Vector<Observer> obs;

	//添加观察者
	public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }

	//删除观察者
	public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }
	
	//通知所有观察者
	public void notifyObservers() {
        notifyObservers(null);
    }

	public void notifyObservers(Object arg) {
        /*
         * a temporary array buffer, used as a snapshot of the state of
         * current Observers.
         */
        Object[] arrLocal;

        synchronized (this) {
            /* We don't want the Observer doing callbacks into
             * arbitrary code while holding its own Monitor.
             * The code where we extract each Observable from
             * the Vector and store the state of the Observer
             * needs synchronization, but notifying observers
             * does not (should not).  The worst result of any
             * potential race-condition here is that:
             * 1) a newly-added Observer will miss a
             *   notification in progress
             * 2) a recently unregistered Observer will be
             *   wrongly notified when it doesn't care
             */
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }

        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }
}
使用JDK提供的类实现观察者模式

通过使用JDK的类来实现上面实例相关的观察模式,自带的观察者的类有Observer接口和Observable类,使用这两个类中的方法可以很好的完成观察者模式,而且JDK帮我们做了相关的加锁操作,保证了线程安全,整体来说会对我们上面的例子进行改进和简化操作,代码如下:

package com.niuh.designpattern.observer.v2;

import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Observable;
import java.util.Observer;

/**
 * 观察者模式
 */
public class ObserverPattern {

    // 步骤6:开始上课,开始记录报告
    public static void main(String[] args) {
        // 创建学生主体
        StudentSubject studentSubject = new StudentSubject();

        // 创建观察者老师
        TeacherObserver teacherObversable = new TeacherObserver();

        // 创建观察者班主任
        HeadTeacherObserver headTeacherObserver = new HeadTeacherObserver();

        // 学生反映上课状态
        studentSubject.setState("讲的不错,很好!");
        studentSubject.addObserver(teacherObversable);
        studentSubject.addObserver(headTeacherObserver);

        // 开始上课
        studentSubject.doNotify();

        // 上课结束
        studentSubject.deleteObserver(headTeacherObserver);
        studentSubject.deleteObserver(teacherObversable);
    }
}


/**
 * 课程类
 */
class Course {

    // 上课时间:time
    private Date time;
    // 上课地点:place
    private String place;
    // 上课内容:content
    private String content;

    public Date getTime() {
        return time;
    }

    public void setTime(Date time) {
        this.time = time;
    }

    public String getPlace() {
        return place;
    }

    public void setPlace(String place) {
        this.place = place;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Course() {
    }

    public Course(Date time, String place, String content) {
        this.time = time;
        this.place = place;
        this.content = content;
    }
}





/**
 * 老师类
 * - 观察者之一
 * - 观察学生的上课情况
 */
class TeacherObserver implements Observer {

    private Course course;

    @Override
    public void update(Observable o, Object arg)  {
        DateFormat df = DateFormat.getTimeInstance(DateFormat.LONG, Locale.CHINA);
        System.out.println("我是王老师,正在讲课中...");

        course = new Course(new Date(), "A栋教学楼", "高等数学");
        System.out.println("今天上课时间:" + df.format(course.getTime()) + " 地点:" + course.getPlace() + " 上课内容:" + course.getContent());
    }
}

/**
 * 班主任来听课
 * - 观察者之一
 * - 观察学生的上课情况
 */
class HeadTeacherObserver implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        System.out.println("我是班主任来听课了,正在检查课程质量...");
        System.out.println("学生反馈课程质量为:" + arg);
    }
}


/**
 * 学生主体
 * - 被观察的对象
 */
class StudentSubject extends Observable {
    /**
     * 上课状态
     */
    private String state;

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    public void doNotify() {
        // 设置标志
        this.setChanged();
        // 通知观察者做出相应动作
        this.notifyObservers(state);
    }
}

输出结果:

我是班主任来听课了,正在检查课程质量...
学生反馈课程质量为:讲的不错,很好!
我是王老师,正在讲课中...
今天上课时间:上午120427秒 地点:A栋教学楼 上课内容:高等数学

PS:以上代码提交在 Githubhttps://github.com/Niuh-Study/niuh-designpatterns.git

文章持续更新,可以公众号搜一搜「 一角钱技术 」第一时间阅读。
本文 GitHub org_hejianhui/JavaStudy 已经收录,欢迎 Star。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值