观察者模式
源码:https://github.com/GiraffePeng/design-patterns
1、定义
指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。主要用于在关联行为之间建立一套触发机制的场景。
2、应用场景
- 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
- 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
3、模式结构
- 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
- 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
- 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
- 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。
4、应用实例
4.1、微信公众号推文
当个人微信号关注一个微信订阅号后,该订阅号在微信公众平台推送的文章能够在每个微信号上查看到。这种一对多的关系,符合观察者模式的功能。这里我们简单模拟下微信公众号推送的实现。
该场景下
- 每个商家订阅号的功能都是一样的,提供了一个抽象功能模板,相当于抽象主题(Subject)角色。
- 每个商家订阅号可以在提供的功能上进行自己的自定义操作,相当于具体主题(Concrete Subject)角色。
- 个人公众号的功能也是相同的,提供了一个抽象功能模板,相当于抽象观察者(Observer)角色。
- 个人公众号自身相当于具体观察者(Concrete Observer)角色。
建立抽象主题角色
//抽象主题角色:抽象目标类,在观察者模式一对多中 扮演 一的角色
public abstract class AbstractWechatSubscription {
protected List<Observer> observers = new ArrayList<Observer>();
//相当于提供了微信号关注订阅号的功能
public AbstractWechatSubscription add(Observer observer) {
observers.add(observer);
return this;
}
//相当于提供了微信号取消关注订阅号的功能
public void remove(Observer observer) {
observers.remove(observer);
}
//声明通知观察者的方法,当该订阅号需要通知微信号推文或者消息时,调用该方法。
public abstract void notifyObserver(String content);
}
建立具体主题角色
//具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
public class WechatSubscription extends AbstractWechatSubscription{
//循环每个观察者,挨个调用其通知方法
@Override
public void notifyObserver(String content) {
for(Observer observer:observers) {
observer.response(content);
}
}
}
建立抽象观察者角色
//抽象观察者,其中声明接到通知时更改的方法
public interface Observer {
public void response(String content);
}
建立具体观察者角色,这里建立两个类
//具体观察者角色 这里相当于微信个人号
public class WechatUserOne implements Observer{
@Override
public void response(String content) {
System.out.println("微信号1接收到微信公众号发来的消息或者推文等,标题:" + content);
}
}
//具体观察者角色 这里相当于微信个人号
public class WechatUserTwo implements Observer{
@Override
public void response(String content) {
System.out.println("微信号2收到微信公众号发来的消息或者推文等,标题:" + content);
}
}
创建测试类,来模拟利用订阅号来完成文章的推送
public class TestWechat {
public static void main(String[] args) {
AbstractWechatSubscription wechatSubscription = new WechatSubscription();
Observer wechatUserOne = new WechatUserOne();
Observer wechatUserTwo = new WechatUserTwo();
//微信号1和微信号2关注了公众号
wechatSubscription.add(wechatUserOne).add(wechatUserTwo);
//发送推文
wechatSubscription.notifyObserver("文章内容");
System.out.println("------------------");
System.out.println("微信号2取消关注了公众号");
//微信号2取消关注该公众号
wechatSubscription.remove(wechatUserTwo);
//发送推文
wechatSubscription.notifyObserver("第二篇文章内容");
}
}
打印结果:
微信号1接收到微信公众号发来的消息或者推文等,标题:文章内容
微信号2收到微信公众号发来的消息或者推文等,标题:文章内容
------------------
微信号2取消关注了公众号
微信号1接收到微信公众号发来的消息或者推文等,标题:第二篇文章内容
看到代码实现,可以发现,目标类(一对多中的一)提供了观察者(一对多中的多)向该目标类中注册的功能,然后当目标类需要通知观察者发生行为改变时,调用该目标类中注册的观察者的相应方法进行通知。
4.2、配置中心(例如cloud的config,携程的apollo等)
在项目方面,配置中心也可以采用观察者模式来实现。将一些properties文件中的属性统一放置于一个服务上,该服务提供统一的属性的配置以及通知其他服务属性的变更。
在该场景下:配置中心相当于具体主题角色,每个服务需要使用的配置往该服务上进行注册,当配置中心发生属性改变时,通知其关联的服务。
建立事件类,通过该事件类来触发属性值的变更通知,因为事件类中维护的属性值针对每个服务来说是一致的,故采用单例模式来实现。
//事件类,存储property的所有属性元素,当配置信息发生改变时,该类中的Properties会发生变化。
public class PropertiesEvent {
private static Properties properties = new Properties();
private PropertiesEvent() {}
public static Properties getProperty() {
return properties;
}
public static String getProperty(String key) {
return properties.getProperty(key);
}
public static void clearProperties() {
properties.clear();
}
public static void addProperty(Object key, Object value) {
if (!properties.containsKey(key)) {
synchronized (properties) {
if (!properties.containsKey(key)) {
properties.put(key, value);
}
}
}
}
public void removeProperty(String key) {
properties.remove(key);
}
}
创建具体主题角色,这里没有建立抽象主题角色是因为该场景下,配置中心是唯一的,并不会有多个配置中心的产生,故设计模式中的模式结构角色可以根据实际场景来发生改变。
//具体主题角色 这里模拟配置中心的具体实现
public class PropertiesSubscription {
protected List<ServiceInterface> services = new ArrayList<ServiceInterface>();
public void addService(ServiceInterface serviceInterface) {
services.add(serviceInterface);
}
public void removeService(ServiceInterface serviceInterface) {
services.remove(serviceInterface);
}
//操作配置中心,对properties属性进行改变
public void updateConfig(Properties properties) {
for (Entry<Object, Object> entry : properties.entrySet()) {
PropertiesEvent.addProperty(entry.getKey(), entry.getValue());
}
refreshConfig();
}
//通知每个服务properties属性发生改变,进行更新
public void refreshConfig() {
for(ServiceInterface serviceInterface:services) {
serviceInterface.refreshConfig(PropertiesEvent.getProperty());
}
}
}
创建抽象观察者角色
//抽象观察者角色
public interface ServiceInterface {
public void refreshConfig(Properties property);
}
创建具体观察者角色,这里相当于每个服务应用
//具体观察者角色 这里模拟订单服务
public class OrderService implements ServiceInterface{
@Override
public void refreshConfig(Properties property) {
System.out.println("orderService更新属性值");
Set<Entry<Object, Object>> entrySet = property.entrySet();
for (Entry<Object, Object> entry : entrySet) {
System.out.println(entry.getKey()+"="+entry.getValue());
}
}
}
//具体观察者角色 这里模拟用户服务
public class UserService implements ServiceInterface{
@Override
public void refreshConfig(Properties property) {
System.out.println("userService更新属性值");
Set<Entry<Object,Object>> entrySet = property.entrySet();
for (Entry<Object,Object> entry : entrySet) {
System.out.println(entry.getKey()+"="+entry.getValue());
}
}
}
建立测试类
public class Test {
public static void main(String[] args) {
PropertiesSubscription propertiesSubscription = new PropertiesSubscription();
//向配置中心注册信息
ServiceInterface userService = new UserService();
ServiceInterface orderService = new OrderService();
propertiesSubscription.addService(userService);
propertiesSubscription.addService(orderService);
//修改配置
Properties properties = new Properties();
properties.put("spring.rabbitmq.host", "127.0.0.1");
properties.put("spring.rabbitmq.port", "5672");
//更新配置信息
propertiesSubscription.updateConfig(properties);
}
}
打印结果:
userService更新属性值
spring.rabbitmq.port=5672
spring.rabbitmq.host=127.0.0.1
orderService更新属性值
spring.rabbitmq.port=5672
spring.rabbitmq.host=127.0.0.1
5 拓展
5.1 基于JDK的Observer
在 Java 中,通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。
- Observable类
Observable 类是抽象目标类,它有一个 Vector 向量,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。
void addObserver(Observer o) 方法:用于将新的观察者对象添加到向量中。
void notifyObservers(Object arg) 方法:调用向量中的所有观察者对象的 update。方法,通知它们数据发生改变。通常越晚加入向量的观察者越先得到通知。
void setChange() 方法:用来设置一个 boolean 类型的内部标志位,注明目标对象发生了变化。当它为真时,notifyObservers() 才会通知观察者。 - Observer 接口
Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 void update(Observable o,Object arg) 方法,进行相应的工作。
加下来我们根据提供的这两个类,来改造上述的配置中心的实现。因为上述的两个类充当抽象角色,故只需要建立具体实现即可。
建立具体主题角色
//具体主题角色 这里模拟配置中心的具体实现
public class PropertiesSubscription extends Observable {
// 操作配置中心,对properties属性进行改变
public void updateConfig(Properties properties) {
super.setChanged();
super.notifyObservers(properties);
}
}
建立具体观察者角色
//具体观察者角色 这里模拟订单服务
@SuppressWarnings("deprecation")
public class OrderService implements Observer{
@Override
public void update(Observable o, Object arg) {
System.out.println("orderService更新属性值");
Properties properties = (Properties)arg;
Set<Entry<Object, Object>> entrySet = properties.entrySet();
for (Entry<Object, Object> entry : entrySet) {
System.out.println(entry.getKey()+"="+entry.getValue());
}
}
}
//具体观察者角色 这里模拟用户服务
@SuppressWarnings("deprecation")
public class UserService implements Observer{
@Override
public void update(Observable o, Object arg) {
System.out.println("userService更新属性值");
Properties properties = (Properties)arg;
Set<Entry<Object, Object>> entrySet = properties.entrySet();
for (Entry<Object, Object> entry : entrySet) {
System.out.println(entry.getKey()+"="+entry.getValue());
}
}
}
编写测试类
public class TestJava {
public static void main(String[] args) {
//向配置中心注册信息
PropertiesSubscription propertiesSubscription = new PropertiesSubscription();
propertiesSubscription.addObserver(new OrderService());
propertiesSubscription.addObserver(new UserService());
//修改配置
Properties properties = new Properties();
properties.put("spring.rabbitmq.host", "127.0.0.1");
properties.put("spring.rabbitmq.port", "5672");
//更新配置信息
propertiesSubscription.updateConfig(properties);
}
}
执行结果:
userService更新属性值
spring.rabbitmq.port=5672
spring.rabbitmq.host=127.0.0.1
orderService更新属性值
spring.rabbitmq.port=5672
spring.rabbitmq.host=127.0.0.1
5.2 基于Guava API
Guava是一种基于开源的Java库,Google Guava源于2007年的"Google Collections Library"。这个库是为了方便编码,并减少编码错误。这个库用于提供集合,缓存,支持原语句,并发性,常见注解,字符串处理,I/O和验证的实用方法。
Guava的好处
- 标准化 - Guava库是由谷歌托管。
- 高效 - 可靠,快速和有效的扩展JAVA标准库
- 优化 -Guava库经过高度的优化。
它是一个提高代码质量、简化工作,促使代码更有弹性、更加简洁的工具。该库里面提供了观察者模式的封装实现。
我们继续改造配置中心,使用Guava来实现。
guava基于注解,彻底将主题角色的抽象以及实现类给封装好了,直接拿来使用,建立具体观察者即可。
创建具体观察者角色
//具体观察者角色 这里模拟订单服务
@SuppressWarnings("deprecation")
public class OrderService{
//基于注解
@Subscribe
public void update(Properties properties) {
System.out.println("orderService更新属性值");
Set<Entry<Object, Object>> entrySet = properties.entrySet();
for (Entry<Object, Object> entry : entrySet) {
System.out.println(entry.getKey()+"="+entry.getValue());
}
}
}
//具体观察者角色 这里模拟用户服务
@SuppressWarnings("deprecation")
public class UserService{
//基于注解
@Subscribe
public void update(Properties properties) {
System.out.println("userService更新属性值");
Set<Entry<Object, Object>> entrySet = properties.entrySet();
for (Entry<Object, Object> entry : entrySet) {
System.out.println(entry.getKey()+"="+entry.getValue());
}
}
}
编写测试类
public class TestGuava {
public static void main(String[] args) {
EventBus eventBus = new EventBus();
//向配置中心注册信息
eventBus.register(new OrderService());
eventBus.register(new UserService());
//修改配置
Properties properties = new Properties();
properties.put("spring.rabbitmq.host", "127.0.0.1");
properties.put("spring.rabbitmq.port", "5672");
//更新配置信息
eventBus.post(properties);
}
}
执行结果
orderService更新属性值
spring.rabbitmq.port=5672
spring.rabbitmq.host=127.0.0.1
userService更新属性值
spring.rabbitmq.port=5672
spring.rabbitmq.host=127.0.0.1
6、优缺点
观察者模式是一种对象行为型模式,其主要优点如下。
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
- 目标与观察者之间建立了一套触发机制。
它的主要缺点如下。
- 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
- 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。