设计模式三十六计之观察者模式(Observer)

设计模式三十六计之观察者模式(Observer)

1. 设计意图

定义对象之间的一对多依赖关系,以便当一个对象更改状态时,将自动通知和更新其所有依赖项。

观察者模式

简而言之

你别来找我,给我你的联系方式,有事我会主动联系你

2.案例演示

以当前最火热的吃鸡游戏作为一个简单的案例来演示观察者模式,当玩家进入游戏时,会收到游戏服务器推送的提示消息,随着游戏的进行,如果某个玩家被Kill掉了,游戏服务器会把此消息推送给房间里的其他玩家。在本案例中,“游戏” 是一个抽象的被观察者,"吃鸡游戏" 是具体的被观察者;“游戏玩家” 是一个抽象的观察者(接口),而玩家A、玩家B等是具体的观察者。案例的UML关系如下图:

image

3. 示例代码

3.1 抽象的被观察者类(Subject)

AbstractGame.java

package com.ramostear.pattern.observer;
import java.util.ArrayList;
/**
 * @author ramostear
 * @create-time 2019/1/5 0005-23:27
 * @modify by :
 * @info:[抽象的被观测者类]
 * @since:
 */
public abstract class AbstractGame {
    /**
     * 定义一个存放观察者的容器
     */
    public final ArrayList<Observer> obsList = new ArrayList<>();
    /**
     * 注册观察者
     * @param obs   观察者
     * @param <T>
     */
    public <T> void attach(Observer obs){
        if (obs == null){
            throw new NullPointerException("Observer is null.");
        }else{
            this.attachObs(obs);
        }
    }
    /**
     * 注册观察者
     * @param obs
     */
    private void attachObs(Observer obs){
        if (obs == null){
            throw new NullPointerException("class is null");
        }else {
            synchronized (obsList){
                if(!obsList.contains(obs)){
                    obsList.add(obs);
                }
            }
        }
    }
    /**
     * 注销观察者
     * @param obs   观察者
     * @param <T>
     */
    public <T> void detach(Observer obs){
        if(obs == null){
            throw new NullPointerException("Observer is null");
        }else {
            this.detachObs(obs);
        }
    }
    /**
     * 注销观察者
     * @param obs
     */
    private void detachObs(Observer obs){
        if(obs == null){
            throw new NullPointerException("Class is null");
        }else{
            synchronized (obsList){
               obsList.remove(obs);
            }
        }
    }
    /**
     * 通知所有的观察者
     * @param messages
     */
    public abstract void notifyAllObs(String...messages);
    /**
     * 通知某个观察者
     * @param obs
     * @param messages
     */
    public abstract void notifyObs(Observer obs,String...messages);
}

AbstractGame类中定义了添加、删除和通知观察者的方法,同时有一个List类型的容器,用于保存已注册的观察者,当需要通知观察者时,从容器中取出观察者信息。

说明:抽象的被观察者可以定义成一个抽象类或者接口,本案例中采用的是抽象类

3.2 抽象的观察者接口(Observer)

Observer.java

package com.ramostear.pattern.observer;

/**
 * @author ramostear
 * @create-time 2019/1/5 0005-23:26
 * @modify by :
 * @info:[观察者接口]
 * @since:
 */
public interface Observer {
    /**
     * 更新状态
     * @param messages
     */
    void update(String... messages);
}

在该接口中定义了一个update() 方法,当被观察者发出通知时,此方法会被调用。

3.3 具体被观察者(ConcreteSubject)

ChikenGame继承了AbstractGame类,并对通知方法进行了具体的实现。
ChikenGame.java

package com.ramostear.pattern.observer;

/**
 * @author ramostear
 * @create-time 2019/1/5 0005-23:55
 * @modify by :
 * @info:[吃鸡游戏类]
 * @since:
 */
public class ChickenGame extends AbstractGame {

    private String roomName;

    public ChickenGame(String roomName) {
        this.roomName = roomName;
    }


    public String getRoomName() {
        return roomName;
    }

    public void setRoomName(String roomName) {
        this.roomName = roomName;
    }

    @Override
    public void notifyAllObs(String... messages) {
        obsList.forEach(obs->{
            this.notifyObs(obs,messages);
        });
    }

    @Override
    public void notifyObs(Observer obs, String... messages) {
       if (obs == null){
           throw new NullPointerException("Observer is null");
       }else{
          obs.update(messages);
       }
    }
}

3.4 具体观察者(ConcreteObserver)

Gamer类实现了Observer接口,并对Observer的update方法进行了具体的实现;这里为了演示,只是简单的对消息进行输出。
Gamer.java

package com.ramostear.pattern.observer;
/**
 * @author ramostear
 * @create-time 2019/1/6 0006-0:06
 * @modify by :
 * @info:[游戏玩家]
 * @since:
 */
public class Gamer implements Observer{

    private String name;

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void update(String... messages) {
        System.out.println("玩家:"+name);
      for (String message:messages){
          System.out.println("消息->"+message+"\n");
      }
    }
}

3.5 测试本次案例

创建一个吃鸡游戏叫“三国吃鸡演义” ,将刘、关、张三个玩家注册到吃鸡游戏中。游戏发布消息给三个玩家,刘、关、张同时收到游戏发出的消息,当关羽挂掉后,只有刘、张两个玩家收到消息;当张飞再挂掉后,只有刘备收到消息。为了演示观察者模式,最后我们让关羽满血复活,此时刘、关二人收到游戏发出的消息。
App.java

package com.ramostear.pattern.observer;
/**
 * @author ramostear
 * @create-time 2019/1/6 0006-0:08
 * @modify by :
 * @info:[测试类]
 * @since:
 */
public class App {
    public static void main(String[] args){
        ChickenGame game = new ChickenGame("三国吃鸡演义");
        Gamer gamerLiu = new Gamer("刘备");
        Gamer gamerZhang = new Gamer("张飞");
        Gamer gamerGuan = new Gamer("关羽");

        game.attach(gamerLiu);
        game.attach(gamerGuan);
        game.attach(gamerZhang);
        game.notifyAllObs("欢迎进入"+game.getRoomName());
        game.notifyAllObs(new String[]{"刘关张桃园三结义,开始三国吃鸡演义..."});

        game.detach(gamerGuan);
        game.notifyAllObs("#关羽:\"我去!被98K爆了,快来扶我一下!\"");
        game.notifyAllObs("#刘备:\"我去,这货肥得一批!\"");

        game.detach(gamerZhang);
        game.notifyAllObs("#张飞:\"我去,这比是挂!\"");
        game.notifyAllObs("#刘备:\"我去!咋这么多人,我凉了!\"");

        game.attach(gamerGuan);
        game.notifyAllObs("关羽满血复活");
        game.notifyAllObs("#刘备:\"苟住,苟住就能赢!\"");

    }
}

测试结果:


image

4. 适用性

当满足以下情况中的一种时使用观察者模式

  • 当抽象有两个Aspect时,一个依赖于另一个。 将这些Aspact封装在单独的对象中可让您独立地改变和重用它们.
  • 当一个对象的更改需要更改其他对象时,你不知道到底需要更改多少个关联的对象
  • 当不希望多个对象之前发生紧耦合时

5. 真实案例

原文链接:https://www.ramostear.com/post/details/observer-design-pattern.html

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

推荐阅读更多精彩内容