观察者模式——看这一篇就够了

一个人可以走的很快,而一群人可以走的很远。

一、基本介绍

描述:

观察者模式又被称为发布订阅(Publish/Subscribe)模式,属于对象行为型模式。它定义对象间的一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,当主题对象状态发生改变时,它的所有观察者都会收到通知并自动更新相关内容。主题是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅并接收通知。

使用场景:

  • 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
  • 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
  • 一个对象必须通知其他对象,而并不知道这些对象是谁,即不希望这些对象是紧密耦合的。

应用实例:

  • 一家报社(Subject)一发布报纸,就会立马派送给所有订报(Observer)的人,订报的人就能获取报纸内容。
  • 用户界面可以作为一个观察者,业务数据是被观察者,用户界面观察业务数据的变化,发现数据变化后,就显示在界面上。

优点:

  • 观察者和被观察者是抽象耦合的,让耦合的双方都依赖于抽象,而不是依赖具体。
  • 对对象解耦,将观察者和被观察者完全隔离。
  • 使用对象间的组合关系,更加灵活。

缺点:

  • 如果一个被观察者对象有很多的直接和间接的观察者,通知所有的观察者会花费很多时间。
  • 如果在观察者和所观察主题之间有循环依赖的话,观察主题会触发它们之间进行循环调用,可能导致系统崩溃。
  • 观察者模式仅仅让观察者知道所观察的主题对象发生了变化,但没有相应的机制让观察者知道所观察的目标主题对象到底是如何发生变化的。

核心类说明:

  • Subject:抽象主题,即被观察对象,本身维护一个观察者集合。
  • Observer:抽象观察者,根据主题状态变化做出相应反应,本身维护一个主题的引用。

注意事项:

  • Java 中已经有了对观察者模式的支持类。
  • 避免循环引用。
  • 如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。

二、利用观察者模式实现订阅报纸

1、设计类图

自己设计观察者模式类图

说明:

  1. Subject接口:定义主题接口,对象使用此接口注册为观察者,或者把自己从观察者中删除,所有实现了此接口的实体类即为具体的主题类。
  2. Observer接口:定义观察者接口,所有潜在的观察者必须实现观察者接口,这个接口只有*update()*一 个方法,当主题状态改变时它被调用。
  3. newspaperOffice实体类:报社类,实现了主题接口,为被观察目标对象。具体的报社类除了实现主题接口中的方法,还提供了获取新闻信息和设置新闻信息方法。报社类设置保存有其观察者列表,为了简单起见使用字符串类型存储新闻信息。
  4. Eric实体类:用户Eric,实现了Observer接口,为观察者对象,并保存有对主题对象的引用。
  5. Shealtiel实体类:用户Shealtiel,实现了Observer接口,为观察者对象,并保存有对主题对象的引用。
  6. 其他实现Subject接口的具体的被观察对象类,其他实现了Observer接口的观察者类。

2、实现代码

(1)Subject主题接口

package observerpattern;

public interface Subject {
    
    
    /**
     * 把观察者对象注册为主题观察者
     *
     * @param o 待注册的观察者对象
     */
    void registerObserver(Observer o);

    /**
     * 从主题观察者表单中删除观察者
     *
     * @param o 待删除的观察者对象
     */
    void removeObserver(Observer o);

    /**
     * 主题对象状态发生改边,通知它的观察者
     */
    void notifyObserver();
}

(2)Observer观察者接口

package observerpattern;

public interface Observer {
    
    
    /**
     * 更新观察者的新闻信息
     *
     * @param news 待更新的信息
     */
    void update(String news);
}

(3)NewspaperOffice报社类,实现Subject接口,为具体的被观察对象。

package observerpattern;

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

/**
 * @Project: DesignPatterns
 * @Date: 2020/2/13 19:56
 * @author: Eric
 * @Description: TODO 报社主题类(被观察者),实现Subject接口,为一具体主题类
 */
public class NewspaperOffice implements Subject {
    
    
    private List<Observer> observers; //报社的观察者列表
    private String news = "";         //报社新闻内容

    public NewspaperOffice() {
    
    
        observers = new ArrayList<>();//初始化观察者列表为空表
    }

    @Override
    public void registerObserver(Observer o) {
    
    
        if (o == null) {
    
    
            throw new NullPointerException();
        }
        if (!observers.contains(o)) {
    
    
            observers.add(o);
        }
    }

    @Override
    public void removeObserver(Observer o) {
    
    
        observers.remove(o);
    }

    @Override
    public void notifyObserver() {
    
    
        for(Observer observer : observers){
    
    
            observer.update(news);  //更新观察者的信息
        }
    }

    /**
     * 设置新闻信息
     *
     * @param news 待设置的新闻信息
     */
    public void setNews(String news) {
    
    
        this.news = news;
        notifyObserver();  //设置新信息后通知观察者
    }
}

(4)Eric用户类,实现Observer接口,订阅报社报纸,为观察者。

package observerpattern;

/**
 * @Project: DesignPatterns
 * @Date: 2020/2/13 20:25
 * @author: Eric
 * @Description: TODO Eric实体类,实现Observer接口,为一具体的观察者
 */
public class Eric implements Observer {
    
    
    private Subject subject;
    private String news;

    public Eric(Subject subject){
    
    
        this.subject = subject;
        this.subject.registerObserver(this); //把当前对象注册为观察者
    }

    @Override
    public void update(String news) {
    
    
        this.news = news;
        showNews(); //更新信息后显示最新信息
    }

    public void showNews(){
    
    
        System.out.println("Eric: "+news);
    }

    /**
     * 取消订阅报纸
     */
    public void unsubscribe(){
    
    
        subject.removeObserver(this);
    }
}

(5)Shealtiel用户类,实现Observer接口,订阅报社报纸,为观察者。

package observerpattern;

/**
 * @Project: DesignPatterns
 * @Date: 2020/2/13 20:25
 * @author: Eric
 * @Description: TODO 订阅者Shealtiel实体类,实现Observer接口,为一具体的观察者
 */
public class Shealtiel implements Observer {
    
    
    private Subject subject;  //对观察的主题的引用
    private String news;

    public Shealtiel(Subject subject){
    
    
        this.subject = subject;
        this.subject.registerObserver(this); //把此对象注册为具体主题的观察者
    }

    @Override
    public void update(String news) {
    
    
        this.news = news;
        showNews(); //更新信息后显示最新信息
    }
    
    /**
	 * 显示新闻信息
	 */
    public void showNews(){
    
    
        System.out.println("Shealtiel: "+news);
    }

    /**
     * 取消订阅报纸,即从主题中去除当前对象
     */
    public void unsubscribe(){
    
    
        subject.removeObserver(this);
    }

}

(6)测试类

package observerpattern;

/**
 * @Project: DesignPatterns
 * @Date: 2020/2/13 20:40
 * @author: Eric
 * @Description: TODO 使用观察者模式模拟订阅报纸测试类
 */
public class Demo {
    
    
    public static void main(String[] args) {
    
    
        //实例化报社类(被观察者),报社类实现了Subject接口,即为具体的主题类
        NewspaperOffice newspaperOffice = new NewspaperOffice();
        
        //实例化用户,并订阅报纸信息,即初始化为报社主题的观察者
        Eric eric = new Eric(newspaperOffice);
        Shealtiel shealtiel = new Shealtiel(newspaperOffice);

        //报社更新信息,系统会自动通知相应的观察者
        System.out.println("报社更新第一条信息,注意观察者接受的信息:");
        newspaperOffice.setNews("This is the first news!");
        
        System.out.println("\n报社更新第二条信息,注意观察者接受的信息:");
        newspaperOffice.setNews("This is the second news!");

        //eric对象取消订阅报纸新闻,则此后的新闻信息将不会通知eric,因为它不再是报社的观察者
        System.out.println("\nEric取消订阅新闻信息");
        eric.unsubscribe();
        
        System.out.println("报社更新第三条信息,注意此时观察者数量:");
        newspaperOffice.setNews("This is the third news!");
    }
}

(7)测试结果

自己设计观察者模式测试结果

三、Java内置的观察者模式

1、设计类图

Java内置的观察者设计模式类图

说明:

  1. Observable类:Java内置可观察类,所有继承扩展此类的实体类为主题类。
  2. Observer接口:Java内置观察者接口,所有观察者必须实现观察者接口,这个接口只有*update()*一 个方法,当主题状态改变时它被调用。

注意一个为类,一个为接口。

Java内置的观察者模式运作方式,和我们自己实现的代码类似,但有一些小差异。最明显的差异是NewspaperOffice(也就是我们的主题)现在扩展自Observable类,并继承到一些增 加、删除、通知观察者的方法(以及其他的方法)。

  • 如何把对象变成观察者?

如同以前一样,实现观察者接口(java.uitl.Observer),然后调用任何Observable主题对象的*addObserver()方法。不想再当观察者时,调用deleteObserver()*方法就可以了。

  • 被观察者要如何送出通知?

首先,需要利用继承扩展java.util.Observable类产生具体的“被观察者”主题类,然后,需要两 个步骤:

  1. 先调用*setChanged()*方法,标记状态已经改变的事实;
  2. 然后调用两种*notifyObservers()*方法中的一个:
notifyObservers()  或  notifyObservers(Object arg)
  • 观察者如何接收通知?

同以前一样,观察者实现了更新的方法,但是方法的签名不太一样:

update(Observable o, Object arg)

如果你想“推”(push)数据给观察者,你可以把数据当作数据对象传送给 notifyObservers(arg)方法。否则,观察者就必须从可观察者对象中“拉”(pull)数据。

  • 了解一下*setChanged()*方法的具体作用。
//伪代码:
setChanged() {
	changed = true //把改变状态设置为true
}

//只有在changed值为true时通知观察者
notifyObservers(Object arg) {
	if (changed) {
    	for every observer on the list {
        	call update (this, arg)
        }
        changed = false
     }
}

notifyObservers() {
	notifyObservers(null)
}

2、使用Java内置观察者模式实现

(1)NewspaperOffice报社类,继承扩展Java内置Observable类,为被观察对象。

package observerpattern;

import java.util.Observable;

/**
 * @Project: DesignPatterns
 * @Date: 2020/2/13 21:34
 * @author: Eric
 * @Description: TODO 报社主题类(被观察者),继承Java内置的Observable类,为一具体主题类
 */
public class NewspaperOffice extends Observable {
    
    
    private String news = "";         //报社新闻内容

    //注意与自己实现的不同处,添加观察者放在超类中处理
    public NewspaperOffice() {
    
    
    }

    /**
     * 更新新闻信息
     */
    public void newsChanged() {
    
    
        setChanged();
        notifyObservers();
    }

    /**
     * 设置新闻信息
     *
     * @param news 待设置的新闻信息
     */
    public void setNews(String news) {
    
    
        this.news = news;
        newsChanged();
    }

    /**
     * 获得新闻信息
     *
     * @return 新闻信息
     */
    public String getNews() {
    
    
        return news;
    }
}

(2)Eric用户类,实现Java内置Observer接口,订阅报社报纸,为观察者。

package observerpattern;

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

/**
 * @Project: DesignPatterns
 * @Date: 2020/2/13 21:41
 * @author: Eric
 * @Description: TODO Eric实体类,实现Java内置的Observer接口,为一具体的观察者
 */
public class Eric implements Observer {
    
    
    private Observable observable;  //主题的引用
    private String news;
    
    public Eric(Observable observable){
    
    
        this.observable = observable;
        this.observable.addObserver(this); //把此对象添加为观察者
    }

    @Override
    public void update(Observable o, Object arg) {
    
    
        if (o instanceof NewspaperOffice) {
    
    
            NewspaperOffice newspaperOffice = (NewspaperOffice) o;
            this.news = newspaperOffice.getNews();
            showNews(); //更新信息后显示信息
        }
    }

    /**
     * 显示信息
     */
    public void showNews(){
    
    
        System.out.println("Eric: "+news);
    }
}

(3)Shealtiel用户类,实现Java内置Observer接口,订阅报社报纸,为观察者。

package observerpattern;

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

/**
 * @Project: DesignPatterns
 * @Date: 2020/2/13 21:47
 * @author: Eric
 * @Description: TODO Shealtiel实体类,实现Java内置的Observer接口,为一具体的观察者
 */
public class Shealtiel implements Observer {
    
    
    private Observable observable; //主题的引用
    private String news;

    public Shealtiel(Observable observable){
    
    
        this.observable = observable;
        this.observable.addObserver(this); //把此对象添加为观察者
    }

    @Override
    public void update(Observable o, Object arg) {
    
    
        if (o instanceof NewspaperOffice) {
    
    
            NewspaperOffice newspaperOffice = (NewspaperOffice) o;
            this.news = newspaperOffice.getNews();
            showNews(); //更新信息后显示信息
        }
    }

    /**
     * 显示信息
     */
    public void showNews(){
    
    
        System.out.println("Shealtiel: "+news);
    }
}

(4)测试类

package observerpattern4;

/**
 * @Project: DesignPatterns
 * @Date: 2020/2/13 21:48
 * @author: Eric
 * @Description: TODO 使用Java内置观察者模式模拟订阅报纸测试类
 */
public class Demo {
    
    
    public static void main(String[] args) {
    
    
        //实例化报社类(被观察者)
        NewspaperOffice newspaperOffice = new NewspaperOffice();
        //实例化订阅者类(观察者)
        Eric eric = new Eric(newspaperOffice);
        Shealtiel shealtiel = new Shealtiel(newspaperOffice);

        //报社更新信息,自动会通知相应的观察者
        System.out.println("报社更新第一条信息,注意观察者接受的信息:");
        newspaperOffice.setNews("This is the first news!");
        System.out.println("\n报社更新第二条信息,注意观察者接受的信息:");
        newspaperOffice.setNews("This is the second news!");

        //eric对象取消订阅报纸新闻,则此后的新闻信息将不会通知eric
        System.out.println("\nEric取消订阅新闻信息");
        newspaperOffice.deleteObserver(eric);

        System.out.println("报社更新第三条信息,注意此时观察者数量:");
        newspaperOffice.setNews("This is the third news!");
    }
}

(5)测试结果

使用Java内置的观察者设计模式测试结果

四、总结

1、自己实现观察者模式思路

首先设计两个接口,一个为主题接口Subject,另一个为观察者接口Observer

然后具体的主题类实现Subject接口,具体的观察者类实现Observer接口。

当某一观察者对象注册为某一主题对象的观察者后,就可以接收此主题对象发布的通知。

2、使用Java内置观察者模式思路

不用自己设计接口,使用Java内置的可观察类Observable和观察者接口Observer

具体的可观察类(主题类)继承扩展Observable类,具体的观察者类实现Observer接口

当某一观察者对象注册为某一主题对象的观察者后,就可以接收此主题对象发布的通知。

注:使用接口能够统一标准,且更加灵活;多用组合,少用继承。

猜你喜欢

转载自blog.csdn.net/weixin_43653599/article/details/104307746