程序员

观察者模式 (Observer)

2017-08-17  本文已影响35人  落叶飞逝的恋

1.定义

定义对象间的一种一对多的依赖关系。当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

2.类图

image

目标对象。具备功能点:1.管理多个观察者。2.提供对观察者的注册及退订。3.当状态发生改变时,对订阅有效的观察者进行通知。

观察者接口。定义目标通知后更新操作

具体的主题,用来维护自身的状态的变化

观察者具体实现对象,用来接受目标的通知,并进行后续的操作。

3.深入理解

建议:

4.代码

/**
 * 目标对象,它知道观察它的观察者,并提供注册和删除观察者的接口
 */
public class Subject {

    /**
     * 用于保存观察者对象
     */
    private List<Observer> observers = new ArrayList<>();

    /**
     * 注册观察者
     *
     * @param observer
     */
    public void attach(Observer observer) {
        observers.add(observer);
    }

    /**
     * 删除观察者对象
     *
     * @param observer
     */
    public void detach(Observer observer) {
        observers.remove(observer);
    }

    /**
     * 通知所有注册的观察者对象
     */
    protected void notifyObservers() {
        observers.stream().forEach(observer -> observer.update(this));
    }
}
/**
 * 观察者接口,定义一个更新的接口给那些在目标发生改变的时候被通知的后的操作
 */
public interface Observer {

    /**
     * 更新接口
     *
     * @param subject
     */
    void update(Subject subject);
}

/**
 * 具体的目标对象,负责把有关状态存入到相应的观察者
 * 并在自己状态发生改变时,通知各个观察者
 */
public class ConcreteSubject extends Subject {

    /**
     * 示意目标对象
     */
    private String subjectState;

    public String getSubjectState() {
        return subjectState;
    }

    public void setSubjectState(String subjectState) {
        this.subjectState = subjectState;
        System.out.println("目标对象已更新自身状态为:" + this.subjectState);
        this.notifyObservers();
    }
}
/**
 * 具体的观察者对象,实现更新方法,使得自身的状态和目标状态保持一致
 */
public class ConcreteObserver implements Observer {

    /**
     * 示意,观察者对象状态
     */
    private String observerState = "呀,我是观察者初始状态!";

    @Override
    public void update(Subject subject) {
        System.out.println("观察者之前的状态为:" + this.observerState);
        observerState = ((ConcreteSubject) subject).getSubjectState();
        System.out.println("观察者接收到更新的对象状态:" + observerState);
    }
}

/**
 * 调用示例
 */
public class ObserverClient {
    public static void main(String[] args) {
        ConcreteSubject subject = new ConcreteSubject();
        Observer observer=new ConcreteObserver();
        subject.attach(observer);
        subject.setSubjectState("121");
    }
}

5. 观察者两种通知模式(推模型和拉模型)

5.1 推模型

目标对象主动向观察者推送目标的详细信息,不管观察者是否需要,推送的信息通常是目标对象的全部或者部分数据,相当于在广播。

5.2 拉模型

目标对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到目标对象中获取,相当于是观察者从目标对象中拉数据。

拉模型的处理方式,会把目标对象自身通过update方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了。

上面的代码就是典型的拉模型。在具体的主题里,通过update方法把自身传递给具体的观察者。

5.3 如何将上面的拉模型转变为推模型

处理步骤

public class PushSubject {
    private List<PushObserver> observers = new ArrayList<>();

    /**
     * 订阅主题
     *
     * @param observer
     */
    public void attach(PushObserver observer) {
        observers.add(observer);
    }

    /**
     * 取消订阅
     *
     * @param observer
     */
    public void detach(PushObserver observer) {
        observers.remove(observer);
    }

    /**
     * 拉模型通知所有的注册者
     */
    protected void notifyObservers(String content) {
        observers.stream().forEach(observer -> {observer.update(content);});
    }
}

public class ConcretePushSubject extends PushSubject {
    /**
     * 示意目标对象
     */
    private String subjectState;

    public String getSubjectState() {
        return subjectState;
    }

    public void setSubjectState(String subjectState) {
        this.subjectState = subjectState;
        System.out.println("目标对象已更新自身状态为:" + this.subjectState);
        this.notifyObservers(subjectState);
    }
}

public interface PushObserver {
    /**
     * 更新接口
     * 传入更新的内容
     * @param content
     */
    void update(String content);
}

public class ConcretePushObserver implements PushObserver {
    @Override
    public void update(String content) {
        System.out.print("主题推送的信息:" + content);
    }
}
public static void main(String[] args) {
    ConcretePushSubject pushSubject=new ConcretePushSubject();
    ConcretePushObserver pushObserver=new ConcretePushObserver();
    pushSubject.attach(pushObserver);
    pushSubject.setSubjectState("1212");
}

5.4 如何选择拉模型与推模型

推模型是假定主题对象知道观察者需要的数据。退模型的一个缺点就是:可能会使得观察者对象难以复用,因为观察者对象的update方法是按需而定义的。ps:为了避免这样的问题,主题对象通常定义一个公用的话题。类比:微信公众号,公众号更新内容,直接推送给订阅的用户。

拉模型是主题对象无法得知观察者具体需要什么数据,干脆把自身对象传递给观察者,让观察者按需提取数据。

6.Java中的观察者

Java语言已经内置好了观察者的实现。

java.util包中定义了Observable,代表具备可以被观察的能力。也就是我们之前定义的Subject类。目标接口

java.util包中也定义了一个接口Observer,代表观察者对象接口。里面定义了update方法

6.1好处

6.2代码

import java.util.Observable;

public class NewPaper extends Observable {
    private String content;

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
        this.setChanged();
        //this.notifyObservers();//拉模型
        this.notifyObservers(content);//推模型
    }
}
import java.util.Observable;
import java.util.Observer;

public class Reader implements Observer {

    //读者名称
    private String name;

    public Reader(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void update(Observable o, Object arg) {
        System.out.println(name + "收到报纸了,目标推送过来的内容是:" + arg);
        //System.out.println(name + "收到报纸了,主动获取到目标更新的内容:" + ((NewPaper) o).getContent());
    }
}
public class Program {
    public static void main(String[] args) {
        NewPaper paper = new NewPaper();
        Reader zhangsan = new Reader("zhangsan");
        Reader lisi = new Reader("lisi");
        paper.addObserver(zhangsan);
        paper.addObserver(lisi);
        paper.setContent("iphone 8发布了!!");
    }
}

7.观察者优缺点

7.1 优点

7.2 缺点

因为不管观察者是否需要,都需要调用update方法。

8.观察者的本质

观察者的本质是:触发联动

当修改目标对象的状态时候,就会触发相应的通知,然后会循环调用所有注册的观察者对象的相应方法。相当于联动调用这些观察者的方法

这个联动还是动态的,可以通过注册和取消注册来控制观察者。同事目标对象和观察者对象的解耦,又保证了无论观察者发生怎样的变化,目标对象总是能够正确的联动过来。

9.何时选用观察者

10.与其他设计模式的区别

10.1 观察者模式与状态模式

两者是有相似之处。但也有所不同,观察者重心是触发联动。但是到底决定哪些观察者进行联动,可以配合状态模式。

当目标状态发生改变时,触发并通知观察者,让观察者去执行相应的操作。

根据不同的状态,选择不同的实现。这个实现类的主要功能就是针对状态相应的操作。

10.2 观察者模式与中介者模式

两个模式可以一起使用。比如观察者模式里面的主题与观察者交互比较复杂,那么就可以一起使用。

上一篇 下一篇

猜你喜欢

热点阅读