和观察者模式来一次约谈

开篇先扯

话说现在的年轻人,很多人每天最困难的的问题是下一顿什么,在哪里吃。作为一个苦逼上班族,食堂饭菜实在是吃腻了。于是乎,今天和一个同事上午不约而同叫了同一家外卖,到了中午吃饭的时间,快递小哥准时送来,我难得吃到了一点跟平常不一样的口味。我表示现在还在心疼自己的钱包...

进入正题

观察者模式概述

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。好吧,这个概念这没什么意思,网上一搜一大把,都大同小异,不多说了。

观察者模式的模型

  • 抽象主题(Subject)角色:抽象主题角色把所有对观察者对象的引用保存在一个聚集(比如ArrayList对象)里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象,抽象主题角色又叫做抽象被观察者(Observable)角色。
  • 具体主题(ConcreteSubject)角色:将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色又叫做具体被观察者(Concrete Observable)角色。
  • 抽象观察者(Observer)角色:为所有的具体观察者定义一个接口,在得到主题的通知时更新自己,这个接口叫做更新接口。
  • 具体观察者(ConcreteObserver)角色:存储与主题的状态自恰的状态。具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态 像协调。如果需要,具体观察者角色可以保持一个指向具体主题对象的引用。

抽象主题一般性代码如下:
package demo;

import java.util.ArrayList;
import java.util.List;
public abstract class Subject {
    private List<Observer> list = new ArrayList<>();

    public void addObserver(Observer observer){
        list.add(observer);
    }

    public void removeObserver(Observer observer){
        list.remove(observer);
    }

    public void notifyObserver(){
        for(Observer o : list){
            o.update();
        }
    }
}

抽相观察者一般性代码如下:

package demo;

public abstract class Observer {
    abstract void update();
}

用例子说明问题

就用叫外卖的例子来说吧,假设某快餐店,供应午餐,有需要的同事在上午订过餐之后,到12点的时候,快递小哥会给每个订过餐的人送来一份蛋炒饭,不同办公楼(公司办公楼分A栋、B栋、C栋...)的同事分别从不同的办公楼下来取餐。用下面的代码实现这个场景:

首先定义抽象主题类:
package demo;

import java.util.ArrayList;
import java.util.List;
public abstract class Subject {
    private List<Observer> list = new ArrayList<>();

    public void addObserver(Observer observer){
        list.add(observer);
    }

    public void removeObserver(Observer observer){
        list.remove(observer);
    }

    public void notifyObserver(String food){
        for(Observer o : list){
            o.update(food);
        }
    }
}

定义抽象观察者类:
package demo;

public abstract class Observer {
    abstract void update(String food);
}

定义具体的主题类,快餐店:
package demo;

public class Neshery extends Subject {
    private String food;
    public void sendFood(String food){
        this.food=food;
        notifyObserver(food);
    }
}

定义具体观察者类,A栋同事:
package demo;

public class A extends Observer{
    @Override
    void update(String food) {
        System.out.println("A栋楼的同事走楼梯下来取走了"+ food);
    }
}

定义具体观察者类,B栋同事:
package demo;

public class B extends Observer{
    @Override
    void update(String food) {
        System.out.println("B栋楼的同事乘电梯下来取走了"+ food);
    }
}

测试类:
package demo;

public class Test {
    public static void main(String[] args) {
        Neshery neshery = new Neshery();
        Observer o1 = new A();
        Observer o2 = new B();
        neshery.addObserver(o1);
        neshery.addObserver(o2);
        neshery.sendFood("蛋炒饭");
    }
}

运行 Test 类,结果如下:
A栋楼的同事走楼梯下来取走了蛋炒饭
B栋楼的同事乘电梯下来取走了蛋炒饭
整个过程就是这样,很好理解,快餐店把订了餐的同事,添加到一个集合,待我把食物送到之后,通知大家,即遍历这个集合,通知每个订餐的人下楼来取餐。至于每栋楼的同事怎么来取餐,这个由每栋楼的人根据实际情况自己去实现。

注意这里面参数的传递,传递的是食物的名字蛋炒饭,我现在蛋炒饭送来了,大家自己来取。
第二天,A栋楼的同事觉得自己这么高下楼梯来取餐太累了,决定早上买面包带上去,第二天,只有B栋楼的同事订餐了,第二天快餐店的食物是宫保鸡丁。修改 Test 类代码如下:
package demo;

public class Test {
    public static void main(String[] args) {
        Neshery neshery = new Neshery();
        Observer o1 = new A();
        Observer o2 = new B();
        neshery.addObserver(o1);
        neshery.addObserver(o2);
        neshery.removeObserver(o1);
        neshery.sendFood("宫保鸡丁");
    }
}

运行结果如下:
B栋楼的同事乘电梯下来取走了宫保鸡丁
这也很好理解,这种业务场景其实很常见,比如,你关注了某个公众号,你被添加到该公众号关注者集合中,它每天推送文章,你就能收到。

观察者模式中的推模式

其实上面介绍的这种观察者模式就叫推模式。
现在考虑另一种场景,每个人的口味不一样,订餐的食物是不同的,假设现在 A 栋楼的同事订的蛋炒饭,B栋楼的同事订的是宫保鸡丁。还能用上面的推模式解决问题吗?废话,当然能。

我们只需要修改抽象主题类的 void notifyObserver(String food) 和抽象观察者类的 void update(String food) 这两个方法的参数就可以了,我可以带上两个参数。修改为下面的代码:
package demo;

import java.util.ArrayList;
import java.util.List;
public abstract class Subject {
    private List<Observer> list = new ArrayList<>();

    public void addObserver(Observer observer){
        list.add(observer);
    }

    public void removeObserver(Observer observer){
        list.remove(observer);
    }

    public void notifyObserver(String aFood,String bFood){
        for(Observer o : list){
            o.update(aFood, bFood);
        }
    }
}

package demo;

public abstract class Observer {
    abstract void update(String aFood, String bFood);
}

package demo;

public class Neshery extends Subject {
    private String aFood;
    private String bFood;
    public void sendFood(String aFood, String bFood){
        this.aFood = aFood;
        this.bFood = bFood;
        notifyObserver(aFood, bFood);
    }
}

package demo;

public class A extends Observer{
    @Override
    void update(String aFood, String bFood) {
        System.out.println("A栋楼的同事走楼梯下来取走了"+ aFood);
    }
}

package demo;

public class B extends Observer{
    @Override
    void update(String aFood, String bFood) {
        System.out.println("B栋楼的同事乘电梯下来取走了"+ bFood);
    }
}

package demo;

public class Test {
    public static void main(String[] args) {
        Neshery neshery = new Neshery();
        Observer o1 = new A();
        Observer o2 = new B();
        neshery.addObserver(o1);
        neshery.addObserver(o2);
        neshery.sendFood("蛋炒饭","宫保鸡丁");
    }
}

再次运行结果如下:
A栋楼的同事走楼梯下来取走了蛋炒饭
B栋楼的同事乘电梯下来取走了宫保鸡丁
这样做确实实现了我们的需求,但是对于A栋楼订餐的人和B栋楼订餐的人来说,传来了多余的参数,感觉就是,我把两份食物都送来了,分别放在了连个储物柜里,把两个储物柜的密码(相当于aFood 和 bFood 的引用)都发给A栋楼和B栋楼订餐的人,你们自己去选,甚至一不小心有选错的可能。
很明显,这样并不好。假设有20个人或者更多的人订餐,那怎么办,你还要通过继续增加参数,来实现业务逻辑吗?或者,你可能并不需要传这么多参数,把所有参数放到一个数组或者集合里,传一个数组或者集合过来,这样代码是好看很多了,但是实际上还是很糟,我给每个人分20条短信发20个存物柜密码,变成给每个人发一条包含20个储物柜的密码。相信不论是送餐小哥还是取餐的人都会崩溃。这就可以用到观察者模式的另一种模式了——拉模式。

观察者模式中的拉模式

拉模式又是怎样的呢?简单点说,就是抽象主题类并不给每个订阅类传相应的内容,而只是通知它们更新了,有订阅者自己去获取更新的内容,有一种“拉取”的意思。具体实现看代码:
package demo;

import java.util.ArrayList;
import java.util.List;
public abstract class Subject {
    private List<Observer> list = new ArrayList<>();
    public void addObserver(Observer observer){
        list.add(observer);
    }

    public void removeObserver(Observer observer){
        list.remove(observer);
    }

    public void notifyObserver(){
        for(Observer o : list){
            o.update(this);
        }
    }
}

package demo;

public abstract class Observer {
    abstract void update(Subject sub);
}

package demo;

public class Neshery extends Subject {
    private String aFood;
    private String bFood;
    public String getAFood(){
        return aFood;
    }

    public String getBFood(){
        return bFood;
    }

    public void sendFood(String aFood, String bFood){
        this.aFood = aFood;
        this.bFood = bFood;
        notifyObserver();
    }
}

package demo;

public class A extends Observer{
    @Override
    void update(Subject sub) {
        if(sub instanceof Neshery){
            System.out.println("A栋楼的同事走楼梯下来取走了"+ ((Neshery)sub).getAFood());
        }
    }
}

package demo;

public class B extends Observer{
    @Override
    void update(Subject sub) {
        if(sub instanceof Neshery){
            System.out.println("A栋楼的同事走楼梯下来取走了"+ ((Neshery)sub).getBFood());
        }
    }
}

package demo;

public class Test {
    public static void main(String[] args) {
        Neshery neshery = new Neshery();
        Observer o1 = new A();
        Observer o2 = new B();
        neshery.addObserver(o1);
        neshery.addObserver(o2);
        neshery.sendFood("蛋炒饭","宫保鸡丁");
    }
}

运行结果没有变化,如下:
A栋楼的同事走楼梯下来取走了蛋炒饭
B栋楼的同事乘电梯下来取走了宫保鸡丁
可以看到,现在抽象主题类中,notifyObserver 方法中把 this 通过 update 方法传给了每个订阅者类对象,并没有传具体更新的内容了。这回好了,快餐做好了,现在不送了,快递小哥给每个订餐的人发一个快餐店地址,大家自己根据地址来取吧。这就是观察者模式中的拉模式。

最后再扯一点

两种模式的比较:

  • 推模型是假定主题对象知道观察者需要的数据;而拉模型是主题对象不知道观察者具体需要什么数据,没有办法的情况下,干脆把自身传递给观察者,让观察者自己去按需要取值。
  • 推模型可能会使得观察者对象难以复用,因为观察者的update()方法是按需要定义的参数,可能无法兼顾没有考虑到的使用情况。这就意味着出现新情况的时候,就可能提供新的update()方法,或者是干脆重新实现观察者;而拉模型就不会造成这样的情况,因为拉模型下,update()方法的参数是主题对象本身,这基本上是主题对象能传递的最大数据集合了,基本上可以适应各种情况的需要。
posted @ 2017-06-15 22:39  SharpCJ  阅读(1066)  评论(2编辑  收藏  举报