观察者模式的Java实现及应用

观察者模式定义

观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。

结构

关键字

  • Observable
    即被观察者,也可以被叫做主题(Subject)是被观察的对象。通常有注册方法(register),取消注册方法(remove)和通知方法(notify)。
  • Observer
    即观察者,可以接收到主题的更新。当对某个主题感兴趣的时候需要注册自己,在不需要接收更新时进行注销操作。

例子与应用

举一个生活中的例子:比如用户从报社订阅报纸,报社和用户之间是一对多依赖,用户可以在报社订阅(register)报纸,报社可以把最新的报纸发给用户(notify),用户自动收到更新。在用户不需要的时候还可以取消注册(remove)。

再比如Android中的EventBus,Rxjava的实现都是基于观察者模式的思想。再比如回调函数:Android中对Button的点击监听等等。

观察者模式可以用来解耦

自己用代码实现一个观察者模式

现在我们用代码来实现上面订阅报纸的例子:
NewProvider作为对于报社的抽象,每隔两秒钟向用户发送报纸;User作为用户的抽象,可以收到报纸。NewsModel作为对报纸本身的抽象。

/**
 * 被观察者接口定义
 */
public interface MyObserverable {

    void register(MyObserver myObserver);

    void remove(MyObserver myObserver);

    void send(NewsModel model);

}

/**
 * 观察者接口定义
 */
public interface MyObserver {

    void receive(NewsModel model);

}
/**
 * 对于报社的抽象,实现了被观察者接口,每隔2s发送一次报纸
 */
public class NewsProvider implements MyObserverable {
    private static final long DELAY = 2 * 1000;
    private List<MyObserver> mObservers; //我们用一个List来维护所有的观察者对象

    public NewsProvider() {
        mObservers = new ArrayList<>();
        generateNews();
    }

    /**
     * 模拟产生新闻,每个2s发送一次
     */
    private void generateNews() {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            int titleCount = 1;
            int contentCount = 1;

            @Override
            public void run() {
                send(new NewsModel("title:" + titleCount++, "content:" + contentCount++));
            }
        }, DELAY, 1000);
    }

    @Override
    public void register(MyObserver myObserver) {
        if (myObserver == null)
            return;
        synchronized (this) {
            if (!mObservers.contains(myObserver))
                mObservers.add(myObserver);
        }
    }

    @Override
    public synchronized void remove(MyObserver myObserver) {
        mObservers.remove(myObserver);
    }

    @Override
    public void send(NewsModel model) {
        for (MyObserver observer : mObservers) {
            observer.receiveNews(model);
        }
    }
}

/**
 * 对于用户的抽象
 */
public class User implements MyObserver {
    private String mName;

    public User(String name) {
        mName = name;
    }

    @Override
    public void receive(NewsModel model) {
        System.out.println(mName + " receive news:" + model.getTitle() + "  " + model.getContent());
    }
}


/**
 * 测试类
 */
public class Test {
    public static void main(String[] args) {

        NewsProvider provider = new NewsProvider();
        User user;
        for (int i = 0; i < 10; i++) {
            user = new User("user:"+i);
            provider.register(user);
        }

    }
}

运行结果如下:

result

这样我们就自己动手完成了一个简单的观察者模式。

其实在JDK的util包内Java为我们提供了一套观察者模式的实现,在使用的时候我们只需要继承Observable和Observer类即可,其实观察者模式十分简单,推荐阅读JDK的实现代码真心没有几行。此外在JDK的实现中还增加了一个布尔类型的changed域,通过设置这个变量来确定是否通知观察者。

下面我们用JDK的类来实现一遍我们的观察者模式:

public class NewsProvider extends Observable {
    private static final long DELAY = 2 * 1000;

    public NewsProvider() {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
           private int titleCount = 1;
           private int contentCount = 1;
            @Override
            public void run() {
                setChanged(); //调用setChagned方法,将changed域设置为true,这样才能通知到观察者们
                notifyObservers(new NewsModel("title:" + titleCount++, "content:" + contentCount++));
            }
        }, DELAY, 1000);
    }
}
public class User implements Observer {
    private String mName;

    public User(String name) {
        mName = name;
    }

    @Override
    public void update(Observable observable, Object data) {
        NewsModel model = (NewsModel) data;
        System.out.println(mName + " receive news:" + model.getTitle() + "  " + model.getContent());
    }
}

非常简单有木有

回调函数与观察者模式

关于回调函数的定义在知乎上看到过一个[很赞的解释][2]:

你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件。回答完毕。

在Android中我们有一个常用的回调:对与View点击事件的监听。现在我们就来分析一下对于View的监听。
通常在我们使用的时候是这样的:

        xxxView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // do something
            }
        });

这样我们就注册好了一个回调函数。
我们可以在View的源码里发现这个接口:

    /**
     * Interface definition for a callback to be invoked when a view is clicked.
     */
    public interface OnClickListener {
        /**
         * Called when a view has been clicked.
         *
         * @param v The view that was clicked.
         */
        void onClick(View v);
    }

当你setClickListener的时候在View的源码中可以看到对本地OnClickListener的初始化

    /**
     * Register a callback to be invoked when this view is clicked. If this view is not
     * clickable, it becomes clickable.
     *
     * @param l The callback that will run
     *
     * @see #setClickable(boolean)
     */
    public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }

当你的点击到一个View后Android系统经过一系列的调用最后到了View的performClick方法中:

/**
     * Call this view's OnClickListener, if it is defined.  Performs all normal
     * actions associated with clicking: reporting accessibility event, playing
     * a sound, etc.
     *
     * @return True there was an assigned OnClickListener that was called, false
     *         otherwise is returned.
     */
    public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        return result;
    }

就在这里,触发了你的onClick方法,然后执行方法体。

这里我们的被观察者就是View,他的注册方法(register)就是setOnClickListener(),通知方法就是performClick;而OnClickListener就是观察者。只不过这里的只能注册一个观察对象而已。
[1]: /img/bVuUQI
[2]: https://www.zhihu.com/question/19801131/answer/13005983#showWechatShareTip

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,194评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,058评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,780评论 0 346
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,388评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,430评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,764评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,907评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,679评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,122评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,459评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,605评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,270评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,867评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,734评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,961评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,297评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,472评论 2 348

推荐阅读更多精彩内容