zoukankan      html  css  js  c++  java
  • 和观察者模式来一次约谈

    开篇先扯

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

    进入正题

    观察者模式概述

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

    观察者模式的模型

    • 抽象主题(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()方法的参数是主题对象本身,这基本上是主题对象能传递的最大数据集合了,基本上可以适应各种情况的需要。
  • 相关阅读:
    11111 Generalized Matrioshkas
    Uva 442 Matrix Chain Multiplication
    Uva 10815 Andy's First Dictionary
    Uva 537 Artificial Intelligence?
    Uva 340 MasterMind Hints
    SCAU 9508 诸葛给我牌(水泥题)
    Uva 10420 List of Conquests(排序水题)
    Uva 409 Excuses, Excuses!
    10/26
    11/2
  • 原文地址:https://www.cnblogs.com/joy99/p/7012770.html
Copyright © 2011-2022 走看看