zoukankan      html  css  js  c++  java
  • 设计模式(二)观察者模式

    一、模式动机

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

    二、模式定义

    在对象之间定义一对多的依赖,当一个对象改变时,依赖它的对象会收到通知,并自动更新。观察者模式又叫做发布-订阅(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对象。

  • 相关阅读:
    在单向链表中删除指定的key
    双向链表反转
    单向链表反转
    认识异或运算
    二分查找
    插入排序
    冒泡排序
    选择排序
    go 语言环境安装
    欧几里得算法
  • 原文地址:https://www.cnblogs.com/sench/p/8878394.html
Copyright © 2011-2022 走看看