【设计模式系列】8.观察者模式

目录

一、观察者模式特点

二、结构分析

三、场景举例

四、Java中对观察者模式的支持


一、观察者模式特点

  • 定义一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己;
  • 观察者与被观察者之间是解耦合的,观察者存在与否不会对被观察者造成影响;
  • 观察者模式也叫发布-订阅模式,或模型-视图模式等,被广泛应用于分布式框架或者组件中,如Zookeeper中的统一配置管理;

二、结构分析

观察者模式下有四种角色:

  • 抽象主题角色(Subject):也叫抽象被观察者角色(Observable),会将所有观察者(Observer)对象的引用保存在一起,每个抽象主题都可以对应多个观察者。抽象主题提供一个接口,可以增减观察者对象。
  • 具体主题角色(ConcreteSubject):实际上是发布者,实现抽象主题提供的接口,实现具体的业务功能,如增删观察者,向登记的观察者通知业务的动态变化。
  • 抽象观察者角色(Observer):也叫更新角色,当得知主题发生变化时,为所有的具体观察者提供一个更新自身状态的接口。
  • 具体观察者角色(ConcreteObserver):实际上是订阅者,实现抽象观察者提供的接口,保持自身变化与主题变化相协调。

三、场景举例

Zookeeper统一配置管理的例子中:

config配置目录节点就是主题角色(发布者),Client1 ~ ClientN 就是观察者角色(订阅者)。

Demo实现:

  • Zookeeper的抽象主题接口:(用于提供发布者具有的功能)
public interface ZKSubject {
    void registerClient(ZKClient zkClient); // 注册监听的客户端
    void deleteClient(ZKClient zkClient); // 删除监听的客户端
    void informAllClient(String newStatus); // 通知所有的客户端
}
  • Zookeeper的具体主题实现:(可增删观察者、向观察者们发出主题变化通知)
public class ZKSubjectConfig implements ZKSubject {
    // 注册并监听的客户端列表
    private List<ZKClient> zkClientList = new ArrayList<>();

    @Override
    public void registerClient(ZKClient zkClient) {
        if (!zkClientList.contains(zkClient)) {
            System.out.println("注册了一个客户端.....");
            zkClientList.add(zkClient);
        }
    }

    @Override
    public void deleteClient(ZKClient zkClient) {
        if (zkClientList.contains(zkClient))
            zkClientList.remove(zkClient);
    }

    @Override
    public void informAllClient(String newStatus) {
        String status = newStatus;
        for (ZKClient zkClient : zkClientList) {
            zkClient.update(status);
        }
    }
}
  • Zookeeper的抽象观察者接口:(用于更新)
public interface ZKClient {
    void update(String status);
}
  • Zookeeper的具体观察者实现:(可以有多个观察者,保持自身与主题变化相一致)
public class ZKClientListener1 implements ZKClient {
    @Override
    public void update(String status) {
        System.out.println("1号客户端收到zk的config节点变化的通知,变化状态为:" + status);
    }
}
public class ZKClientListener2 implements ZKClient{
    @Override
    public void update(String status) {
        System.out.println("2号客户端收到zk的config节点变化的通知,变化状态为:" + status);
    }
}

测试:

@Test
void testObserver() {
    // 创建主题和观察者(即发布者和订阅者,一对多)
    ZKSubjectConfig subject = new ZKSubjectConfig();
    ZKClientListener1 clientListener1 = new ZKClientListener1();
    ZKClientListener2 clientListener2 = new ZKClientListener2();
    // 注册客户端,客户端会放入监听主题的客户端列表中
    subject.registerClient(clientListener1);
    subject.registerClient(clientListener2);
    // 发布者写入配置文件配置,向所有监听它的客户端通知这件事情
    subject.informAllClient("写入配置消息");
}

结果:

四、Java中对观察者模式的支持

在java.util包下提供了Observer类作为观察者,Observable类作为被观察者,看一下源码:

// 观察者
public interface Observer {
    void update(Observable o, Object arg);
}
// 被观察者
public class Observable {
    private boolean changed = false;
    private Vector<Observer> obs;
    
    public Observable() {obs = new Vector<>();}
    
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }
    
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }
    
    public void notifyObservers() {
        notifyObservers(null);
    }
    
    public void notifyObservers(Object arg) {
        Object[] arrLocal;
        synchronized (this) {
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }
        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }
    
    protected synchronized void setChanged() {changed = true;}
    
    protected synchronized void clearChanged() {changed = false;}
    
    public synchronized boolean hasChanged() {return changed;}
}

接着上面的例子看下如何使用:

// 被观察者状态改变的实现类
public class SubjectStatus extends Observable {
    private String status = "";

    public String getStatus() {
        return status;
    }

    public void changeStatus(String newStatus){
        if (!newStatus.equals(this.status)){
            this.status = newStatus;
            setChanged();
            notifyObservers();
        }
    }
}
// 第1个观察者
public class Client1 implements Observer {
    // 通过构造器把自己加入监听被观察着的列表中
    public Client1(Observable o) {
        System.out.println("Client1已加入监听列表....");
        o.addObserver(this);
    }

    @Override
    public void update(Observable o, Object arg) {
        System.out.println("Client1收到节点变化通知,状态为:" + ((SubjectStatus) o).getStatus());
    }
}
// 第2个观察者
public class Client2 implements Observer {
    // 通过构造器把自己加入监听被观察着的列表中
    public Client2(Observable o) {
        System.out.println("Client2已加入监听列表....");
        o.addObserver(this);
    }

    @Override
    public void update(Observable o, Object arg) {
        System.out.println("Client2收到节点变化通知,状态为:" + ((SubjectStatus) o).getStatus());
    }
}

测试一下:

@Test
void testObserver() {
    // 被观察者
    SubjectStatus sc = new SubjectStatus();
    // 多个观察者
    Client1 c1 = new Client1(sc);
    Client2 c2 = new Client2(sc);
    logger.info("\n当前监听者的个数:" + sc.countObservers());
    sc.changeStatus("新增配置");
}

输出结果:

【不积硅步无以至千里,共同进步吧~】

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值