设计模式(十一):观察者模式(ObserverPattern)

star2017 1年前 ⋅ 425 阅读

观察者模式 又称为 发布 / 订阅(Publish / Subscribe)模式、源 / 监听模式(Source / Listener)。

观察者模式定义对象之间的一对多的依赖关系, 一个对象的行为发生改变,所有依赖于它的对象的行为也发生改变。观察者模式是对象的行为模式。

如果有了解 Servlet 监听器或消息中间件的 发布 / 订阅 模式的应该很好理解观察者模式。

在现实世界中,许多对象并不是独立存在的,其中一个对象发生改变可能导致一个或多个其它对象的行为也发生改变。例如,交叉路口的红绿灯与行人和车的关系,人和车会接收到红绿类灯改变的信号,从而做出相应的行为。

同样,软件系统也存在某一个对象发生改变时,某些其他对象做出相应的改变。

模式定义

观察者模式 定义了对象之间一对多的依赖关系,一个对象发生改变时通知其它对象,其它对象相应的做出改变自己的行为的反应。即多个观察者同时监听(或订阅)某一个目标对象,这个目标对象在状态上发生变化时,会通知所有观察者对象,使它们能够做出相应的行为改变。

发生改变的对象称为 观察目标,即被观察的,而被通知的对象称为观察者,一个观察者目标可以对应多个观察者,而观察者之间可以是没有相互联系的。

观察者模式,状态模式,策略模式都属于行为模式:

  • 策略模式:定义一组算法,算法可灵活选择,是行为选择。
  • 状态模式:对像自身属性发生改变(状态迁移),从而改变自身的行为。
  • 观察者模式:接收到外部条件改变的通知,从而改变自身的行为。

模式分析

观察者模式,描述的是对象之间一对多的依赖关系,其中关键是 观察目标 和 观察者,一个观察目标可以有多个观察者,一旦观察目标发生改变时发生通知到观察者,观察者接收通知并做出相应的行为反应。

观察目标是通知的发布者,广播的方式发送通知到所有观察者,并不需要知道观察者的细节。

观察目标的通知是通过调用成员方法做到的,成员方法里取出维护的所有观察者对象,并使其调用自己方法。

模式结构

观察者模式包含如下角色:

  • 抽象观察者(Observer):为所有具体观察者定义一个接口,在接收到目标的通知时更新自己(即行为方法),可以是抽象类或接口来实现。
  • 具体观察者(Concrete Observer):实现抽象接口的方法,用于接收目标的通知(被目标调用)。
  • 抽象主题(Subject):被观察者抽象,维护观察者列表,提供用于增加和删除观察者对象的接口,一般用一个抽象类或一个接口实现。
  • 具体主题(Concrete Subject):给所有观察者发送通知(调用观察者方法实现)。

结构图与代码

完整版

有抽象观察者角色,具体观察者角色,抽象主题角色,具体主题角色。

在主题对象中维护所有的观察者,由主题发起通知所有观察者做出变化。

观察者模式结构图

抽象主题:定义添加、删除、通知观察者的方法

/**
 * 定义添加、删除、通知观察者的方法
 */
public interface ISubject {

    void add(IObserver observer);

    void remove(IObserver observer);

    void someChange(Object String);

    void notifyObserver(Object String);
}

具体主题:实现抽象主题,维护所有观察者对象,主题发生变化时,取出遍历维护的所有观察者对象发起通知。

public class ConcreteSubject implements ISubject {

    private Vector<IObserver> vector = new Vector<>();

    @Override
    public void add(IObserver observer) {
        vector.add(observer);
    }

    @Override
    public void remove(IObserver observer) {
        vector.remove(observer);

    }

    @Override
    public void someChange(Object String) {
        System.out.println("-----> some change:" + String);
        notifyObserver(String);
    }

    @Override
    public void notifyObserver(Object String) {
        Enumeration enumeration = observers();
        while (enumeration.hasMoreElements()){
            ((IObserver)enumeration.nextElement()).update(String);
        }

    }

    private Enumeration observers() {
        return ((Vector)vector.clone()).elements();
    }
}

抽象观察者:定义接收消息更新自己行为的方法

/**
 * 定义接收消息更新自己行为的方法
 */
public interface IObserver {

    void update(Object String);
}

具体观察者:实现抽象观察者的方法

public class ConcreteObserverA implements IObserver {
    @Override
    public void update(Object String) {
        System.out.println("观察者A收到通知,更新自己行为....... " + String);
    }
}

public class ConcreteObserverB implements IObserver {
    @Override
    public void update(Object String) {
        System.out.println("观察者B收到通知,更新自己行为....... " + String);
    }
}

客户端:创建主题对象,创建该主题的所有观察者对象,把观察者对象维护在主题中。在主题中发起变化通知。

public class Client {

    public static void main(String[] args) {
        ISubject subject = new ConcreteSubject();
        IObserver observerA = new ConcreteObserverA();
        subject.add(observerA);
        IObserver observerB = new ConcreteObserverB();
        subject.add(observerB);
        subject.someChange("Hello World");
    }
}

结果输出:

-----> some change:Hello World
观察者A收到通知,更新自己行为....... Hello World
观察者B收到通知,更新自己行为....... Hello World

简化版

省略了抽象主题,直接就一主题类;有抽象观察者角色和具体观察者角色,

观察者模式结构图

主题角色:维护所有观察者对象,主题发生变化时,取出遍历维护的所有观察者对象发起通知。

/**
 * 主题
 */
public class Subject {

    // 维护观察者容器
    private List<AbstractObserver> observerList = new ArrayList<>();
    private Object content;

    public Object getContent() {
        return content;
    }

    //状态改变,调用通知观察者方法
    public void someChange(Object content) {
        this.content = content;
        notifyAllObservers();
    }

    public void add(AbstractObserver observer) {
        observerList.add(observer);
    }

    public void notifyAllObservers() {
        for (AbstractObserver observer : observerList) {
            observer.update(content);
        }
    }
}

抽象观察者:保存了一个主题,由具本观察者对象构造方法入参。

/**
 * 抽象观察者
 */
public abstract class AbstractObserver {

    protected Subject subject;

    abstract void update(Object content);
}

具体观察者:实现抽象观察者方法,在具体观察者里把自己添加到主题维护所有观察者的容器中。

public class ConcreteObserverA extends AbstractObserver {

    public ConcreteObserverA(Subject subject) {
        super.subject = subject;
        subject.add(this);
    }

    @Override
    public void update(Object content) {
        System.out.println("观察者A收到通知,更新自己行为.......");
    }
}

public class ConcreteObserverB extends AbstractObserver {

    public ConcreteObserverB(Subject subject) {
        super.subject = subject;
        subject.add(this);
    }

    @Override
    public void update(Object content) {
        System.out.println("观察者B收到通知,更新自己行为.......");
    }
}

客户端:创建主题对象,创建该主题的所有具体观察者对象。

public class Client {

    public static void main(String[] args) {
        Subject subject = new Subject();
        new ConcreteObserverA(subject);
        new ConcreteObserverB(subject);
        subject.someChange("Hello World");
    }
}

结果输出

观察者A收到通知,更新自己行为.......
观察者B收到通知,更新自己行为.......

单例版

把主题定义成一个单例类,在创建具体观察者对象在,在构造方法里拿到单例主题对象并将自己维护到主题对象中的容器中。

单例主题:私有化构造方法,初始化主题对象,对外提供主题对象方法。定义通知观察者方法。

public class Subject {

    private static Subject subject = new Subject();

    // 维护观察者容器
    private List<AbstractObserver> observerList = new ArrayList<>();
    private Object content;

    private Subject() {
    }

    public static Subject getInstance() {
        return subject;
    }


    public Object getContent() {
        return content;
    }

    //状态改变,调用通知观察者方法
    public void someChange(Object content) {
        this.content = content;
        notifyAllObservers();
    }

    public void add(AbstractObserver observer) {
        observerList.add(observer);
    }

    public void notifyAllObservers() {
        for (AbstractObserver observer : observerList) {
            observer.update(content);
        }
    }
}

抽象观察者:定义接收通知变更自己行为的方法。

public abstract class AbstractObserver {
    abstract void update(Object content);
}

具体观察者:实现抽象观察者方法,将自己添加到主题中维护观察者对象的容器中。

public class ConcreteObserverA extends AbstractObserver {

    public ConcreteObserverA() {
        Subject.getInstance().add(this);
    }

    @Override
    public void update(Object content) {
        System.out.println("观察者A收到通知,更新自己行为.......");
    }
}

public class ConcreteObserverB extends AbstractObserver {

    public ConcreteObserverB() {
        Subject.getInstance().add(this);
    }

    @Override
    public void update(Object content) {
        System.out.println("观察者B收到通知,更新自己行为.......");
    }
}

客户端:创建具体体观察者对象,主题发行变化。

public class Client {

    public static void main(String[] args) {
        new ConcreteObserverA();
        new ConcreteObserverB();
        Subject.getInstance().someChange("Hello World");
    }
}

结果输出

观察者A收到通知,更新自己行为.......
观察者B收到通知,更新自己行为.......

细节区别

  • 完整版:主题对象自己维护观察者列表,手动调用通知方法。
  • 简化版:省略了抽象主题角色,观察者自己维护主题对象持有的观察者列表,主题在发变化时就可通知观察者。
  • 单例版:把简化版中的主题定义为一个单例类。

完整版简化版 的区别主要在于思想转变,联想下控制反转,实际应用中建议采用示简化版的方式,在新增扩展修改观察者时,不需要修改主题对象,即发布消息者是不需要修改原码,可以灵活增加订阅者,满足 开闭原则

再思考下 Servlet 的监听器,我们可以自定义 Session 监听器、Request 监听器、ServletContext 监听器,只需要订阅即可,不会修改发布者,实现了观察者与被观察者的解耦(隔离)。

优缺点

优点

  1. 降低了观察者与被观察者之间的耦合,两者之间是抽象耦合关系。

    被观察者不需知道具体观察者的类型,不需要认识一个具体观察者,只需知道它们的抽象类型,有一个共同的接口。

  2. 观察者模式支持广播通信。被观察者会向维护的所有观察者发送通知。

缺点

  1. 当观察者过式时,通知所有观察者可能耗时较长,影响效率。
  2. 如果被观察者之间存在循环依赖的话,被观察者会触发它们之间的循环调用,可能导致系统崩溃。

适用场景

  1. 对象之间存在一对多的关系,一个主题对象发生改变会影响其它对象的形为。
  2. 没有相应的机制使观察者知道被观察的对是怎么发送变化的。

模式应用

从 AWT 1.1 开始视窗系统的事件模型采用观察者模式,因此观察者模式在 Java 语言中的地位是比较重要的,JDK 提供了对观察者模式的支持。

JDK 支持

java.util 库中,提供了一个 Observable 类以及一个 Observer 接口,构造 Java 语言对观察者模式的支持。

Observer 接口

public interface Observer {
    /**
     * 观察目标发生改变时被调用
     */
    void update(Observable o, Object arg);
}

只定义了一个 update() 方法 。当被观察者对象的状态发生变化时,被观察者对象的 notifiObservers() 方法就会调用此方法。

Observable 类

Observable 里的方法都是同步的,是线程安全的。Observable 代表一个被观察者对象,一个被观察者对象可以有多个观察者对象,每个观察者都是实现 Observer 接口的对象。

在被观察者对象发生变化时,它会调用 Observable 的 notifyObservers 方法,此方法调用所有的具体观察者的 update() 方法,从而使所有的观察者都被通知更新自己。

Observable 发送通知的顺序默认是按照观察者对象(Observer) 登记先后的相反顺序。但在 Observable 类的子类可以重写通知顺序。子类还可以在单独的线程里通知观察者对象,或在一个公用的线程里按照次序执行。

public class Observable {
    private boolean changed = false;
    // 存放所有观察者对象的引用 
    private Vector<Observer> obs;

    public Observable() {
        obs = new Vector<>();
    }
    /**
     * 添加观察者
     */
    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);
    }
    /**
     * 通知观察者,调用所有观察者对象的 update() 方法
     */
    public void notifyObservers() {
        notifyObservers(null);
    }

    public void notifyObservers(Object arg) {

        Object[] arrLocal;

        synchronized (this) {
            if (!changed)
                return;
            //临时存放当前的观察者的状态
            arrLocal = obs.toArray();
            clearChanged();
        }

        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }
    /**
     * 移除所有
     */
    public synchronized void deleteObservers() {
        obs.removeAllElements();
    }
    /**
     * 设置内部标记,代表观察者对象的状态发生了变化
     */
    protected synchronized void setChanged() {
        changed = true;
    }

    protected synchronized void clearChanged() {
        changed = false;
    }

    public synchronized boolean hasChanged() {
        return changed;
    }

    public synchronized int countObservers() {
        return obs.size();
    }
}

JDK 应用示例

主题:继承 JDK 的 Observable,创建改变状态的方法,并调用通知方法。

/**
 * 被观察者
 */
public class Watched extends Observable {

    private String data = "";

    public String getDate(){
        return data;
    }

    public void changeDate(String data){
        if(!this.data.equals(data)){
            this.data = data;
            setChanged();
        }

        notifyObservers();
    }

}

观察者:实现观察者抽象接口,重写 update() 方法。

注意,构造方法里传入了主题对象,观察者将自己加入到主题维护的观察者容器中。

/**
 * 观察者
 */
public class Wathcer implements Observer {

    /**
     * 观察者主动将自己加入到被观察者维护的观察者聚集中
     * @param watched
     */
    public Wathcer(Watched watched) {
        watched.addObserver(this);
    }

    @Override
    public void update(Observable o, Object arg) {

        System.out.println("Data has been changed to :" + ((Watched)o).getDate());

    }
}

客户端调用

public class MainTester {

    private static Watched watched;
    private static Observer watcher;

    public static void main(String[] args) {
        watched = new Watched();
        watcher = new Wathcer(watched);

        watched.changeDate("Change First");
        // 两次相同状态
        watched.changeDate("Change Tow");
        watched.changeDate("Change Tow");
        watched.changeDate("Change Four");
        watched.changeDate("Change Five");
    }
}

结果输出

Data has been changed to :Change First
Data has been changed to :Change Tow
Data has been changed to :Change Four
Data has been changed to :Change Five

紧邻相同的状态,只执行了一次,内部判断状态未改变。

Servlet 监听器

Servlet 引擎的事件可分两种,一种是 Servlet 环境事件(上下文 事件),包含 ServletContextListener 和 ServletContextAttributeListener;另一种是 Session 事件,包含 HttpSessionListener 和 HttpSessionAttributeListener 。

这些监听器接口( 抽象观察者接口)都是 java.util.EventListener 的子接口,抽象接口提供了方法用于通知时被调用。

监听器实现原理

Servlet 的事件监听实现是观察者模式的典型应用, 这里以 HttpSessionListener 为例。

管理者抽象:Tomcat 中 的 org.apache.catalina.session.ManagerBase 管理者抽象,里面的 createSession()方法创建 Session。

@Override
public Session createSession(String sessionId) {

    if ((maxActiveSessions >= 0) &&
        (getActiveSessions() >= maxActiveSessions)) {
        rejectedSessions++;
        throw new TooManyActiveSessionsException(
            sm.getString("managerBase.createSession.ise"),
            maxActiveSessions);
    }

    // Recycle or create a Session instance(回收或创建 Session 实例)
    Session session = createEmptySession();

    // Initialize the properties of the new session and return it
    session.setNew(true);
    session.setValid(true);
    session.setCreationTime(System.currentTimeMillis());
    session.setMaxInactiveInterval(getContext().getSessionTimeout() * 60);
    String id = sessionId;
    if (id == null) {
        id = generateSessionId();
    }
    //设置会话ID(被观察者状态)
    session.setId(id);
    sessionCounter++;

    SessionTiming timing = new SessionTiming(session.getCreationTime(), 0);
    synchronized (sessionCreationTiming) {
        sessionCreationTiming.add(timing);
        sessionCreationTiming.poll();
    }
    return (session);
}

具体主题:在 Tomcat 中的 org.apache.catalina.session.StandardSession (具体被观察者),实现了 Session(抽象被观察者) 接口中的方法 。

在具体主题 StandardSession 的 setId() 方法中调用了观察者(监听器)中的方法。

@Override
public void setId(String id) {
    setId(id, true);
}

@Override
public void setId(String id, boolean notify) {

    if ((this.id != null) && (manager != null))
        manager.remove(this);

    this.id = id;

    if (manager != null)
        manager.add(this);

    if (notify) {
        //调用通知监听器方法
        tellNew();
    }
}


/**
 * 通知监听器有新的Session
 */
public void tellNew() {

    // Notify interested session event listeners
    fireSessionEvent(Session.SESSION_CREATED_EVENT, null);

    // Notify interested application event listeners
    Context context = manager.getContext();
    // 取出监听器容器
    Object listeners[] = context.getApplicationLifecycleListeners();
    if (listeners != null && listeners.length > 0) {
        HttpSessionEvent event =
            new HttpSessionEvent(getSession());
        // 遍历监听器
        for (int i = 0; i < listeners.length; i++) {
            // 判断监听器类型
            if (!(listeners[i] instanceof HttpSessionListener))
                continue;
            // 取出具体监听器, HttpSessionListener 类型
            HttpSessionListener listener =
                (HttpSessionListener) listeners[i];
            try {
                context.fireContainerEvent("beforeSessionCreated",
                                           listener);
                // 调用监听器的方法(观察者更新方法)
                listener.sessionCreated(event);
                context.fireContainerEvent("afterSessionCreated", listener);
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                try {
                    context.fireContainerEvent("afterSessionCreated",
                                               listener);
                } catch (Exception e) {
                    // Ignore
                }
                manager.getContext().getLogger().error
                    (sm.getString("standardSession.sessionEvent"), t);
            }
        }
    }
}

具体观察者:自定义了 HttpSessionListener 接口类型的监听器,实现了里面的方法,则在 Session 创建或销毁时被通知到。

例如通过 Session 在线数来统以在线用户数。

其它参考

  1. 推荐《Java 与 模式》--阎宏,非常好。
  2. 观察者模式(Observer模式)详解
  3. 图说设计模式-观察者模式
  4. 观察者模式在 Spring 中的应用
更多内容请访问:IT源点

相关文章推荐

全部评论: 0

    我有话说: