设计模式(二)观察者模式

一、模式动机

建立对象之间的一对多关系,一个对象发生改变时会自动通知其他对象,其他对象将做出相应的反应。其中,发生改变的对象叫做主题,被通知的对象叫做观察者。在使用过程中可以根据需要增加和删除观察者,这样系统扩展就变得非常容易。

二、模式定义

在对象之间定义一对多的依赖,当一个对象改变时,依赖它的对象会收到通知,并自动更新。观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。

三、模式结构

观察者模式包含以下4个角色:

1、Subject:抽象主题角色,包含了所有观察者的引用(比如可以保存在ArrayList中),通知观察者以及添加、删除观察者的抽象方法;

2、ConcreteSubject:具体主题角色,继承自Subject,实现了其中的抽象方法;

3、Observer:抽象观察者角色,包含了一个更新自身状态的抽象方法,在获知主题更新后,将调用此方法;

4、ConcreteObserver:具体观察者角色,继承自Observer,实现了更新自身状态的抽象方法。如果有必要,则还可以保存主题的引用。

 四、代码实现

抽象主题类Subject:

import java.util.ArrayList;
import java.util.List;

public abstract class Subject {

    private List<Observer> observerList = new ArrayList<>();

    /**
     * 添加观察者
     * @param observer
     */
    public void attach(Observer observer){
        observerList.add(observer);
    }

    /**
     * 删除观察者
     * @param observer
     */
    public void detach(Observer observer){
        observerList.add(observer);
    }

    /**
     * 通知观察者
     * @param newState
     */
    public void notifyObservers(String newState){
        for(Observer observer:observerList){
            observer.update(newState);
        }
    }
}

具体主题类ConcreteSubject:

public class ConcreteSubject extends Subject {

    private String subjectState;

    public void setNewState(String newState){
        this.subjectState = newState;
        System.out.println("主题状态发生改变,新的状态为:" + newState);
        this.notifyObservers(newState);
    }

}

抽象观察者接口Observer:

public interface Observer {

    /**
     * 观察者更新自身状态
     * @param newState 新的状态
     */
    public void update(String newState);

}

具体观察者类ConcreteObserverA:

public class ConcreteObserverA implements Observer {

    private String oberverState;

    @Override
    public void update(String newState) {
        oberverState = newState;
        System.out.println("观察者A状态已改变,新的状态为:" + newState);
    }
}

具体观察者类ConcreteObserverB:

public class ConcreteObserverB implements Observer{

    private String observerState;

    @Override
    public void update(String newState) {
        observerState = newState;
        System.out.println("观察者B状态已改变,新的状态为:" + newState);
    }
}

在客户端类Client中进行测试:

public class Client {

    public static void main(String[] args){
        ConcreteSubject subject = new ConcreteSubject();

        Observer observerA = new ConcreteObserverA();
        subject.attach(observerA);
        Observer observerB = new ConcreteObserverB();
        subject.attach(observerB);

        // 主题状态改变
        subject.setNewState("new state");
    }

}

运行结果:

主题状态发生改变,新的状态为:new state
观察者A状态已改变,新的状态为:new state
观察者B状态已改变,新的状态为:new state

五、观察者模式中的推和拉

主题的状态中有时会包含很多信息,在上面的例子中,我们把主题中的所有信息都推给了观察者,但观察者可能只需要这些信息中的一部分,实现方法是在主题中将要推送给观察者的信息字段上添加getter方法,然后在观察者更新状态的方法中传入主题的引用(推方式中传入的是主题所有的数据),并通过主题的getter方法来拉取所需要的数据。

用观察者拉取的方式重写上面的代码:

抽象主题类:

import java.util.ArrayList;
import java.util.List;

public abstract class Subject {

    private List<Observer> observerList = new ArrayList<>();

    /**
     * 添加观察者
     * @param observer
     */
    public void attach(Observer observer){
        observerList.add(observer);
    }

    /**
     * 删除观察者
     * @param observer
     */
    public void detach(Observer observer){
        observerList.add(observer);
    }

    /**
     * 通知观察者
     * @param subject 主题引用
     */
    public void notifyObservers(Subject subject){
        for(Observer observer:observerList){
            observer.update(this);
        }
    }
}

具体主题类ConcreteSubject:

public class ConcreteSubject extends Subject {

    private String subjectState;

    public void setNewState(String newState){
        this.subjectState = newState;
        System.out.println("主题状态发生改变,新的状态为:" + newState);
        this.notifyObservers(this);
    }

    public String getState(){
        return subjectState;
    }

}

观察者接口:

public interface Observer {

    /**
     * 观察者更新自身状态
     * @param subject 主题引用
     */
    public void update(Subject subject);

}

具体观察者类ConcreteObserverA:

public class ConcreteObserverA implements Observer {

    private String oberverState;

    @Override
    public void update(Subject subject) {
        oberverState = ((ConcreteSubject)subject).getState();
        System.out.println("观察者A状态已改变,新的状态为:" + oberverState);
    }
}

具体观察者类ConcreteObserverB:

public class ConcreteObserverB implements Observer{

    private String observerState;

    @Override
    public void update(Subject subject) {
        observerState = ((ConcreteSubject)subject).getState();
        System.out.println("观察者B状态已改变,新的状态为:" + observerState);
    }
}

在客户端Client中进行测试:

public class Client {

    public static void main(String[] args){
        ConcreteSubject subject = new ConcreteSubject();

        Observer observerA = new ConcreteObserverA();
        subject.attach(observerA);
        Observer observerB = new ConcreteObserverB();
        subject.attach(observerB);

        // 主题状态改变
        subject.setNewState("new state");
    }

}

结果:

主题状态发生改变,新的状态为:new state
观察者A状态已改变,新的状态为:new state
观察者B状态已改变,新的状态为:new state

 六、使用JDK内置的观察者模式

在java.util包中,有一个Obsever接口和一个Observable类,分别对应上文中的Observer接口和Subject类,实现了Observer接口的类就是观察者,继承了Observable类的类就是主题(被观察者),这样对主题来讲,使用addObserver(Observer)方法来添加观察者,使用deleteObserver(Observer)方法来删除观察者,当主题发生改变时,使用以下步骤通知观察者:

1、调用setChanged()方法,标记状态已经改变的事实;

2、调用方法notifyObservers()或者notifyObservers(Object object)来通知观察者,前一个方法适用于观察者拉取数据,后一种方法可以在通知的时候,传递任何的数据对象给观察者,主题将数据推给了观察者。

当观察者发现主题发生改变时,则调用自身的update(Observable o, Object arg)来更新自身状态,其中前一个参数标识了观察者所观察的主题,后一个则是主题调用notifyObservers方法时传入的数据对象,如果没有则为空。

代码实现:

主题类(被观察者类):

import java.util.Observable;

public class ConcreteSubject extends Observable {

    private String subjectState;

    /**
     * 主题(被观察者)状态改变
     * @param newState
     */
    public void setState(String newState){
        subjectState = newState;
        System.out.println("主题状态改变,新的状态是:" + subjectState);
        System.out.println("通知观察者");

        setChanged();
        notifyObservers();
    }

    /**
     * 让观察者拉取数据
     * @return
     */
    public String getSubjectState(){
        return subjectState;
    }
}

观察者类:

import java.util.Observable;
import java.util.Observer;

public class ConcreteObserver implements Observer {

    private String observerState;

    public ConcreteObserver(Observable observable){
        observable.addObserver(this);
    }

    @Override
    public void update(Observable o, Object arg) {
        observerState = ((ConcreteSubject)o).getSubjectState();
        System.out.println("观察者状态改变,新的状态是:" + observerState);
    }
}

使用客户端Client来测试:

public class Client {

    public static void main(String[] args){
        ConcreteSubject subject = new ConcreteSubject();
        ConcreteObserver observer = new ConcreteObserver(subject);

        subject.setState("new state");
    }

}

结果如下:

主题状态改变,新的状态是:new state
通知观察者
观察者状态改变,新的状态是:new state

在JDK中,还有其他应用体现了观察者模式,比如在一个按钮上添加监听器(ActionListener),当按钮被按下时(主题发生改变),触发ActionListener中的相应逻辑。 

六、观察者模式的优缺点

优点:

1、在观察者模式下,主题和观察者之间实现了松耦合。主题只知道观察者实现了一个接口(Observer接口),主题并不需要关心观察者具体的类是谁,做了些什么等其他细节,添加或删除观察者,主题不会受到影响,我们可以独立地复用主题和观察者,而且系统也变得很有弹性,易于扩展。

缺点:

1、当观察者数目较多时,主题可能要花费较长时间通知观察者;

2、观察者之间有循环依赖的话,被观察者会触发他们之间的循环调用,从而导致系统崩溃。

七、适用场景

1、一个对象的改变将导致其他对象发生改变,而不需要关心有多少对象需要跟着改变;

2、一个对象需要通知其他对象,但不需要知道这些对象的具体情况;

3、可以使用观察者模式实现链式触发机制,当对象A发生改变,则影响B对象,B对象的改变又会影响C对象。

posted @ 2018-04-19 15:41  ColdCode  阅读(275)  评论(0编辑  收藏  举报
AmazingCounters.com