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

1. 设计意图

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

观察者模式

简而言之

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

2.案例演示

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

UML类图-案例对象关系

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("#刘备:\"苟住,苟住就能赢!\"");

    }
}

测试结果:
观察者模式案例测试结果

4. 适用性

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

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

5. 真实案例