• 和观察者模式来一次约谈


    开篇先扯

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

    进入正题

    观察者模式概述

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

    观察者模式的模型

    • 抽象主题(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()方法的参数是主题对象本身,这基本上是主题对象能传递的最大数据集合了,基本上可以适应各种情况的需要。
  • 相关阅读:
    LeetCode 43 字符串相乘
    HDU 1031 Design T-Shirt
    HDU 1728 逃离迷宫
    HDU 1285 确定比赛名次
    HDU 1116 Plays on words
    HDU 1195 Open the lock
    HDU 1072 Nightmare
    HDU 1272 小希的迷宫
    HDU 1273 漫步森林
    HDU 1269 迷宫城堡
  • 原文地址:https://www.cnblogs.com/joy99/p/7012770.html
Copyright © 2020-2023  润新知