天问

  • Blog
  • 热修复与动态加载
  • python爬虫
  • 专栏
    • 热修复与动态加载
    • NDK开发
    • 设计模式与架构
    • React Native
  • 好文转载
    • 计算机技术
    • 投资理财
    • 摄影与拍照技巧
  • 投稿
  • 关于本站及隐私政策
  • Github
StevenQ
为今天工作,为明天投资,为未来孵化一些东西!
  1. 首页
  2. 计算机技术
  3. 设计模式
  4. 正文

【设计模式之】观察者模式

2018年12月8日 501点热度 4人点赞 0条评论

观察者模式是开发中使用非常频繁的一个模式,用的最多地方就是将UI层和业务逻辑层解耦,比如安卓中ListView控件所关联的Adapter的数据变化时通知ListView控件界面更新。观察者模式和发布订-阅模式很类似,很多开发者觉得观察者模式和发布-订阅模式是一回事,但我个人觉得可以认为观察者模式是简化版的发布-订阅模式,但实际上这2者有着本质的区别,具体的区别请看文末的说明。前者比后者要简单的多,后者多用在跨组件消息传递上,用来简化组件间的通信,如EventBus。

应用场景

  • 将UI层和业务层逻辑解耦
  • 跨组件传递消息(此时实际上是发布-订阅模式)

首先来考虑如下一个日常生活场景:用户订阅新闻中心的文章,当新闻中心发布新文章之后,每一个订阅过该新闻中心服务的用户都能收到该消息。对于这个简单的例子,大家可能觉得这很简单,完全用不着使用观察者模式,高级开发者都能不假思索的写出如下代码:

class Article {
    private String title;
    private String author;
    private String content;

    public Article(String title, String author, String content) {
        this.title = title;
        this.author = author;
        this.content = content;
    }

    @Override
    public String toString() {
        return "Article{" +
                "title='" + title + '\'' +
                ", author='" + author + '\'' +
                ", content='" + content + '\'' +
                '}';
    }
}

class User {
    private String name;

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

    public void update(Article article) {
        System.out.println(name + "  获取到了新文章:" + article.toString());
    }
}

class PostCenter {
    private List<User> users = new ArrayList<>();

    public void postArticle(Article article) {
        System.out.println("发布中心发布了文章:" + article.toString());
        for (User u : users) {
            u.update(article);
        }
    }

    public void registerUser(User u) {
        users.add(u);
    }

}

测试用例和运行结果如下:

  public static void main(String[] args) {
        PostCenter postCenter = new PostCenter();
        User user = new User("小明");
        User user2 = new User("小红");

        postCenter.registerUser(user);
        postCenter.registerUser(user2);

        postCenter.postArticle(new Article("设计模式系列", "www.huqi.tech", "设计模式之观察者模式"));
        postCenter.postArticle(new Article("爬虫系列", "www.huqi.tech", "爬虫入门爬取妹纸图"));

    }
发布中心发布了文章:Article{title='设计模式系列', author='www.huqi.tech', content='设计模式之观察者模式'}
小明 获取到了新文章:Article{title='设计模式系列', author='www.huqi.tech', content='设计模式之观察者模式'}
小红 获取到了新文章:Article{title='设计模式系列', author='www.huqi.tech', content='设计模式之观察者模式'}

发布中心发布了文章:Article{title='爬虫系列', author='www.huqi.tech', content='爬虫入门爬取妹纸图'}
小明 获取到了新文章:Article{title='爬虫系列', author='www.huqi.tech', content='爬虫入门爬取妹纸图'}
小红 获取到了新文章:Article{title='爬虫系列', author='www.huqi.tech', content='爬虫入门爬取妹纸图'}

但是实际上上述代码本质上就是观察者模式的思想,只不过没显示的使用java内置的Observer/Observable接口而已。我们看下观察者模式的定义:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。 从上面的代码实现来看是完全符合该定义的:PostCenter类的内部维护一个集合List用来保存订阅了消息推送服务的用户,对外提供一个注册接口registerUser用来记录添加订阅该服务的用户对象,当PostCenter发布新消息时从集合List中遍历所有的用户对象,调用User的update函数通知用户获取到了新文章。也就是说上面的代码本质上已经使用了观察者模式,只不过你还没意识到而已。

对于这个例子而言只要你的代码逻辑是让发布中心持有所有订阅该服务的用户对象,然后当发布中心发布新消息时,让发布中心来调用用户的某个接口通知用户,那么本质上就是观察者模式。因为它符合观察者模式的定义。如果不使用观察者模式,也就是不是让发布中心来通知用户新消息发布了,而是让用户自己去看是否有新消息发布。那么这个实现逻辑就非常复杂了,就只能让用户每隔一段时间轮询发布中心的消息了,就类似你在你们楼下小卖部买一件东西,这件东西暂时缺货,那么有2种解决办法,第一种就是你留下手机号码,等货到了之后小卖部老板打电话通知你,第二种就是你每隔几天去逛下,看货是否到了。前者就是观察者模式的思想,后者不是。所以我个人感觉观察者模式不像一种设计模式,更像一种编程技巧,对于这个场景如果你不使用观察者模式,那么实现起来会非常复杂。

或许也正因为观察者模式应用非常广泛,java已经内置了对观察者模式相关接口的支持,也就是Observer/Observable。上述代码如果使用Observer/Observable接口改写如下:

/**
 * 用户:观察者
 */
class User implements Observer {

    private String name;

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

    @Override
    public void update(Observable o, Object arg) {
        if (arg instanceof Article) {
            System.out.println(name + " 获取到了新文章:" + arg.toString());
        }
    }
}

/**
 * 文章发布中心:被观察者
 * 在UML图中也被称作subject
 */
class PostCenter extends Observable {
    /**
     * 发布新文章
     *
     * @param article 新的文章
     */
    public void postNewArticle(Article article) {
       System.out.println("发布中心发布了文章:" + article.toString());
       // 设置状态已改变
       setChanged();
       // 在发布文章的时候,通知所有观察者
       notifyObservers(article);

    }

}

class Article {
    private String title;
    private String author;
    private String content;

    public Article(String title, String author, String content) {
        this.title = title;
        this.author = author;
        this.content = content;
    }

    @Override
    public String toString() {
        return "Article{" +
                "title='" + title + '\'' +
                ", author='" + author + '\'' +
                ", content='" + content + '\'' +
                '}';
    }
}

测试用例如下:

 public static void main(String[] args) {
        PostCenter postCenter = new PostCenter();
        User user = new User("小明");
        User user2 = new User("小红");

        postCenter.addObserver(user);
        postCenter.addObserver(user2);

        postCenter.postNewArticle(new Article("设计模式系列", "www.huqi.tech", "设计模式之观察者模式"));
        postCenter.postNewArticle(new Article("爬虫系列", "www.huqi.tech", "爬虫入门爬取妹纸图"));
    }

运行结果与前面完全一致。这里实际上是Observable(可观察的,也就是被观察者)这个类内部维护了一个集合用来保存观察者,当调用该类的addObserver方法时就会把这些观察者添加到该集合中,当调用setChanged();和 notifyObservers(article);方法之时就会从该集合中遍历所有的观察者(实现了Observer接口),然后调用Observer接口的update方法通知观察者。

也就是把定义对象间的一种一对多的关系以及所有依赖于它的对象都得到通知并被自动更新这2个细节隐藏掉了,而最开始的代码相当于是我们自己实现了这部分细节。

观察者模式知名项目分析

最典型的莫过于安卓中使用ListView时Adapter的数据变化时通知ListView控件界面更新了。这种应用场景就是前面提到的第一种将UI层和业务层逻辑解耦的绝佳典范。当我们添加或删除了某些数据后都会使用notifyDataSetChanged();函数来通知数据改变刷新界面,如下所示:

public class MyAdapter<T> extends BaseAdapter {
    private ArrayList<T> mData;

    //...省略其他逻辑...
    
    public void add(T data) {
        if (mData == null) {
            mData = new ArrayList<>();
        }
        mData.add(data);
        notifyDataSetChanged();
    }

    public void remove(T data) {
        if (mData != null) {
            mData.remove(data);
        }
        notifyDataSetChanged();
    }
    
    //...省略其他逻辑...
}

我们以该函数为切入点分析下:

 public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
        private final DataSetObservable mDataSetObservable = new DataSetObservable();

        // ...省略其他不相关细节代码...

        /**
         * Notifies the attached observers that the underlying data has been changed
         * and any View reflecting the data set should refresh itself.
         */
        public void notifyDataSetChanged() {
            mDataSetObservable.notifyChanged();
        }
    }

跟进DataSetObservable的notifyChanged方法

 public class DataSetObservable extends Observable<DataSetObserver> {
        /**
         * Invokes {@link DataSetObserver#onChanged} on each observer.
         * Called when the contents of the data set have changed.  The recipient
         * will obtain the new contents the next time it queries the data set.
         */
        public void notifyChanged() {
            synchronized (mObservers) {
                // since onChanged() is implemented by the app, it could do anything, including
                // removing itself from {@link mObservers} - and that could cause problems if
                // an iterator is used on the ArrayList {@link mObservers}.
                // to avoid such problems, just march thru the list in the reverse order.
                for (int i = mObservers.size() - 1; i >= 0; i--) {
                    mObservers.get(i).onChanged();
                }
            }
        }

        // ...省略其他不相关细节代码...
    }

可以看到DataSetObservable 继承了Observable类,该方法本质上是遍历Observable类的mObservers集合中的观察者,然后调用观察者的onChanged方法。跟进Observable类,验证下我们的猜想:

 public abstract class Observable<T> {
        /**
         * The list of observers.  An observer can be in the list at most
         * once and will never be null.
         */
        protected final ArrayList<T> mObservers = new ArrayList<T>();

        /**
         * Adds an observer to the list. The observer cannot be null and it must not already
         * be registered.
         * @param observer the observer to register
         * @throws IllegalArgumentException the observer is null
         * @throws IllegalStateException the observer is already registered
         */
        public void registerObserver(T observer) {
            if (observer == null) {
                throw new IllegalArgumentException("The observer is null.");
            }
            synchronized(mObservers) {
                if (mObservers.contains(observer)) {
                    throw new IllegalStateException("Observer " + observer + " is already registered.");
                }
                mObservers.add(observer);
            }
        }

        /**
         * Removes a previously registered observer. The observer must not be null and it
         * must already have been registered.
         * @param observer the observer to unregister
         * @throws IllegalArgumentException the observer is null
         * @throws IllegalStateException the observer is not yet registered
         */
        public void unregisterObserver(T observer) {
            if (observer == null) {
                throw new IllegalArgumentException("The observer is null.");
            }
            synchronized(mObservers) {
                int index = mObservers.indexOf(observer);
                if (index == -1) {
                    throw new IllegalStateException("Observer " + observer + " was not registered.");
                }
                mObservers.remove(index);
            }
        }

        /**
         * Remove all registered observers.
         */
        public void unregisterAll() {
            synchronized(mObservers) {
                mObservers.clear();
            }
        }
    }

可以看到这个类主要作用就是定义一个集合用来保存观察者对象,同时提供注册和解注册函数用来将观察者添加到集合中/从集合中删除。和我们最开始的那个代码逻辑类似,只不过这里是把相关逻辑抽取到了Observable抽象类中。

观察者模式与发布订阅模式的区别

最大的区别就是观察者模式中观察者和被观察者直接进行消息传递(注意这里的直接是从消息传递过程来说的,不是从代码逻辑上),而发布订阅模式是通过一个调度中心间接传递消息,发布者不直接与订阅者通信。用图描述如下所示:

举个日常生活的小例子就很容易明白了。假设你打算在你们小区苹果手机专卖店买一部最新款苹果手机,你去了之后发现没货了,于是店主和你说可以留下你的电话号码(将你注册为该店的观察者),等货到了之后打电话通知你。同样小明去了,也没货,也留下了他的电话号码。过了几天,货到了,店主直接打电话联系你说货到了。这就是观察者模式。在这个例子,你和小明是观察者,店主是被观察者。店主(被观察者)货到了之后(数据更新时)会直接打电话通知你和小明(遍历所有的观察者向他们发送更新消息)。在这个例子中店主和你是直接进行消息通信的。

如果是发布订阅模式,那么这个例子应该是这样的:

你到小区苹果专卖店之后发现没货了,这时店主和你说可以扫描我们小店的二维码,关注我们小店的公众号,当货到了之后,我们会在公众号上发布到货通知。于是你关注了该店的二维码,同样的小明也关注了。过了1天,小明取消关注了,再过了几天,货到了,然后店主发布了到货通知,你从公众号上接受到了消息,知道货到了。这就是发布订阅模式。发布者不直接与订阅者进行消息通信,而是通过一个调度中心(微信公众号)间接通信。这么做的一个很明显的好处就是店主不需要一个一个顾客打电话去通知,节省了好多话费,店主(发布者)只需要向微信公众号(调度中心)发布一个到货通知。店主也不关心小明是否取消关注了,他只负责向公众号发布消息。订阅者也可以随时取消订阅。

关于这2者区别更详细权威介绍可参看:https://hackernoon.com/observer-vs-pub-sub-pattern-50d3b27f838c

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: 设计模式
最后更新:2019年2月24日

天问

追梦追自由,穷则独善其身,达则兼济天下

打赏 点赞
< 上一篇
下一篇 >

COPYRIGHT © 2022 天问 ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang