前言:

观察者模式 亦称: 事件订阅者、监听者、Event-Subscriber、Listener、Observer,是一种行为设计模式, 允许你定义一种订阅机制, 可在对象事件发生时通知多个 “观察” 该对象的其他对象。根据应用场景的不同,观察者模式会对应不同的代码实现方式:有同步阻塞的实现方式,也有异步非阻塞的实现方式;有进程内的实现方式,也有跨进程的实现方式。目的是将观察者和被观察者代码解耦。

GoF 的《设计模式》定义:在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。

目录

一、结构

二、代码实现

三、观察者模式在Google guava中的应用

四、观察者模式适合应用场景

五、观察者模式优缺点

六、与其他模式的关系


一、结构

  1. 发布者 (Publisher) 会向其他对象发送值得关注的事件。 事件会在发布者自身状态改变或执行特定行为后发生。 发布者中包含一个允许新订阅者加入和当前订阅者离开列表的订阅构架。

  2. 当新事件发生时, 发送者会遍历订阅列表并调用每个订阅者对象的通知方法。 该方法是在订阅者接口中声明的。

  3. 订阅者 (Subscriber) 接口声明了通知接口。 在绝大多数情况下, 该接口仅包含一个 update更新方法。 该方法可以拥有多个参数, 使发布者能在更新时传递事件的详细信息。

  4. 具体订阅者 (Concrete Subscribers) 可以执行一些操作来回应发布者的通知。 所有具体订阅者类都实现了同样的接口, 因此发布者不需要与具体类相耦合。

  5. 订阅者通常需要一些上下文信息来正确地处理更新。 因此, 发布者通常会将一些上下文数据作为通知方法的参数进行传递。 发布者也可将自身作为参数进行传递, 使订阅者直接获取所需的数据。

  6. 客户端 (Client) 会分别创建发布者和订阅者对象, 然后为订阅者注册发布者更新。

二、代码实现

  学生订阅期刊:

/*** @Description: 观察者接口 订阅期刊** @Author HJW* @Date 2021/4/23* @Version V1.0**/
public interface Subscriber {void update(String type, String body);
}/*** @Description: 学生观察者** @Author HJW* @Date 2021/4/23* @Version V1.0**/
public class Student implements Subscriber{/*** 学号*/private String number;/*** 名称*/private String name;public Student(String number, String name) {this.number = number;this.name = name;}@Overridepublic void update(String type, String body) {System.out.println(name + "期刊类型:" + type + "  期刊内容:" + body);}public String getNumber() {return number;}public void setNumber(String number) {this.number = number;}public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "Student{" +"number='" + number + '\'' +", name='" + name + '\'' +'}';}
}
/*** @Description: 期刊发布者** @Author HJW* @Date 2021/4/23* @Version V1.0**/
public interface Publisher {/*** 订阅** @param type 期刊类型* @param student 学生*/void subscribe(String type, Subscriber student);/*** 取消订阅** @param type 期刊类型* @param student 学生*/void unsubscribe(String type,  Subscriber student);/*** 发布信息** @param type 期刊类型*  @param body 期刊内容*/void publish(String type, String body);
}/*** @Description: 报刊实际发布者** @Author HJW* @Date 2021/4/23* @Version V1.0**/
public class PeriodicalPublisher implements Publisher{/*** 订阅各类期刊的学生*/private static Map<String, List<Subscriber>> subscriberMap = new HashMap<>(16);/*** 期刊类型*/private static Set<String> periodicalTypeSet = new HashSet<>();static {periodicalTypeSet.add("科技类");periodicalTypeSet.add("历史类");periodicalTypeSet.add("计算机类");periodicalTypeSet.add("哲学类");periodicalTypeSet.add("娱乐类");periodicalTypeSet.forEach(periodicalType -> {List<Subscriber> studentList = new ArrayList<>();subscriberMap.put(periodicalType, studentList);});}/*** 订阅** @param type   期刊类型* @param student 学生*/@Overridepublic void subscribe(String type, Subscriber student) {if (!periodicalTypeSet.contains(type)) {System.out.println("暂时无" + type + "期刊,敬请期待!!!");return;}subscriberMap.get(type).add(student);System.out.println(student + "订阅" + type + "期刊");}/*** 取消订阅** @param type   期刊类型* @param student 学生*/@Overridepublic void unsubscribe(String type, Subscriber student) {if(subscriberMap.get(type).contains(student)) {subscriberMap.get(type).remove(student);System.out.println(student + "取消订阅" + type + "类期刊");}}/*** 发布信息** @param type 期刊类型* @param body 期刊内容*/@Overridepublic void publish(String type, String body) {subscriberMap.get(type).forEach(student -> {student.update(type, body);});}/*** 获取期刊类型** @return periodicalTypeSet*/public static Set<String> getPeriodicalTypeSet() {return periodicalTypeSet;}/*** 获取期刊类型** @return periodicalTypeSet*/public static void addPeriodicalTypeSet(String type) {periodicalTypeSet.add(type);List<Subscriber> studentList = new ArrayList<>();subscriberMap.put(type, studentList);}}
/*** @Description: 客户端测试类** @Author HJW* @Date 2021/4/23* @Version V1.0**/
public class Client {public static void main(String[] args) {Student studentA = new Student("001", "张三");Student studentB = new Student("002", "李四");Student studentC = new Student("003", "王五");Student studentD = new Student("004", "赵六");Student studentE = new Student("005", "田七");List<Student> studentList = new ArrayList<>();studentList.add(studentB);studentList.add(studentA);studentList.add(studentC);studentList.add(studentD);studentList.add(studentE);PeriodicalPublisher publisher = new PeriodicalPublisher();Set<String> periodicalTypeSet = PeriodicalPublisher.getPeriodicalTypeSet();// 学生订阅期刊studentList.forEach(student -> {periodicalTypeSet.forEach(periodical -> {publisher.subscribe(periodical, student);});});// 期刊发布内容publisher.publish("科技类", "科技发展");publisher.publish("历史类", "5000年文明历史");publisher.publish("计算机类", "人工智能");publisher.publish("哲学类", "马克思主义");publisher.publish("娱乐类", "开心每一天");// 某学生取消某类期刊publisher.unsubscribe("娱乐类", studentC);publisher.unsubscribe("哲学类", studentA);// 期刊发布内容publisher.publish("科技类", "科技发展");publisher.publish("历史类", "5000年文明历史");publisher.publish("计算机类", "人工智能");publisher.publish("哲学类", "马克思主义");publisher.publish("娱乐类", "开心每一天");}
}

三、观察者模式在Google guava中的应用

各类信息:

/*** @Description: 消息接口* @Author HJW* @Date 2021/4/24* @Version V1.0**/
public interface Messge {
}/*** @Description: TODO* @Author HJW* @Date 2021/4/24* @Version V1.0**/
public class XMsg implements Messge{private String title;private String body;public XMsg(String title, String body) {this.title = title;this.body = body;}@Overridepublic String toString() {return "XMsg{" +"title='" + title + '\'' +", body='" + body + '\'' +'}';}
}/*** @Description: TODO* @Author HJW* @Date 2021/4/24* @Version V1.0**/
public class YMsg implements Messge{private String title;private String body;public YMsg(String title, String body) {this.title = title;this.body = body;}@Overridepublic String toString() {return "XMsg{" +"title='" + title + '\'' +", body='" + body + '\'' +'}';}
}/*** @Description: TODO* @Author HJW* @Date 2021/4/24* @Version V1.0**/
public class ZMsg implements Messge{private String title;private String body;public ZMsg(String title, String body) {this.title = title;this.body = body;}@Overridepublic String toString() {return "XMsg{" +"title='" + title + '\'' +", body='" + body + '\'' +'}';}
}

各观察者:


/*** @Description: a观察者* * @Author HJW* @Date 2021/4/24* @Version V1.0**/
public class AObserver {@Subscribepublic void resMessage(XMsg event) {System.out.println(event);}}/*** @Description: B观察者* @Author HJW* @Date 2021/4/24* @Version V1.0**/
public class BObserver {@Subscribepublic void resMessageY(YMsg event) {System.out.println(event);}@Subscribepublic void resMessageZ(ZMsg event) {System.out.println(event);}}/*** @Description: TODO* @Author HJW* @Date 2021/4/24* @Version V1.0**/
public class ZObserver {@Subscribepublic void resMessage(ZMsg event) {System.out.println(event);}}

模拟 实现gauva EventBus

​/*** @Description: 标明观察者中的哪个函数可以接收消息* * @Author HJW* @Date 2021/4/24* @Version V1.0**/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Beta
public @interface Subscribe {
}/*** @Description: ObserverAction 类用来表示 @Subscribe 注解的方法,* 其中,target 表示观察者类,method 表示方法。它主要用在 ObserverRegistry 观察者注册表中。* * @Author HJW* @Date 2021/4/24* @Version V1.0**/
public class ObserverAction {private Object target;private Method method;public ObserverAction(Object target, Method method) {this.target = Preconditions.checkNotNull(target);this.method = method;this.method.setAccessible(true);}public void execute(Object event) { // event是method方法的参数try {method.invoke(target, event);} catch (InvocationTargetException | IllegalAccessException e) {e.printStackTrace();}}
}/*** @Description: 注册表,是最复杂的一个类,框架中几乎所有的核心逻辑都在这个类中。* 这个类大量使用了 Java 的反射语法,不过代码整体来说都不难理解,其中,一个比较有技巧的地方是 CopyOnWriteArraySet 的使用。* CopyOnWriteArraySet,顾名思义,在写入数据的时候,会创建一个新的 set,并且将原始数据 clone 到新的 set 中,* 在新的 set 中写入数据完成之后,再用新的 set 替换老的 set。这样就能保证在写入数据的时候,不影响数据的读取操作,以此来解决读写并发问题。除此之外,* CopyOnWriteSet 还通过加锁的方式,避免了并发写冲突。具体的作用你可以去查看一下 CopyOnWriteSet 类的源码,一目了然。* * @Author HJW* @Date 2021/4/24* @Version V1.0**/
public class ObserverRegistry {private ConcurrentMap<Class<?>, CopyOnWriteArraySet<ObserverAction>> registry = new ConcurrentHashMap<>();public void register(Object observer) {Map<Class<?>, Collection<ObserverAction>> observerActions = findAllObserverActions(observer);for (Map.Entry<Class<?>, Collection<ObserverAction>> entry : observerActions.entrySet()) {Class<?> eventType = entry.getKey();Collection<ObserverAction> eventActions = entry.getValue();CopyOnWriteArraySet<ObserverAction> registeredEventActions = registry.get(eventType);if (registeredEventActions == null) {registry.putIfAbsent(eventType, new CopyOnWriteArraySet<>());registeredEventActions = registry.get(eventType);}registeredEventActions.addAll(eventActions);}}public List<ObserverAction> getMatchedObserverActions(Object event) {List<ObserverAction> matchedObservers = new ArrayList<>();Class<?> postedEventType = event.getClass();for (Map.Entry<Class<?>, CopyOnWriteArraySet<ObserverAction>> entry : registry.entrySet()) {Class<?> eventType = entry.getKey();Collection<ObserverAction> eventActions = entry.getValue();if (postedEventType.isAssignableFrom(eventType)) {matchedObservers.addAll(eventActions);}}return matchedObservers;}private Map<Class<?>, Collection<ObserverAction>> findAllObserverActions(Object observer) {Map<Class<?>, Collection<ObserverAction>> observerActions = new HashMap<>();Class<?> clazz = observer.getClass();for (Method method : getAnnotatedMethods(clazz)) {Class<?>[] parameterTypes = method.getParameterTypes();Class<?> eventType = parameterTypes[0];if (!observerActions.containsKey(eventType)) {observerActions.put(eventType, new ArrayList<>());}observerActions.get(eventType).add(new ObserverAction(observer, method));}return observerActions;}private List<Method> getAnnotatedMethods(Class<?> clazz) {List<Method> annotatedMethods = new ArrayList<>();for (Method method : clazz.getDeclaredMethods()) {if (method.isAnnotationPresent(Subscribe.class)) {Class<?>[] parameterTypes = method.getParameterTypes();Preconditions.checkArgument(parameterTypes.length == 1,"Method %s has @Subscribe annotation but has %s parameters."+ "Subscriber methods must have exactly 1 parameter.",method, parameterTypes.length);annotatedMethods.add(method);}}return annotatedMethods;}}/*** @Description: EventBus 实现的是阻塞同步的观察者模式* * @Author HJW* @Date 2021/4/24* @Version V1.0**/
public class EventBus {private Executor executor;private ObserverRegistry registry = new ObserverRegistry();public EventBus() {this(MoreExecutors.directExecutor());}protected EventBus(Executor executor) {this.executor = executor;}public void register(Object object) {registry.register(object);}public void post(Object event) {List<ObserverAction> observerActions = registry.getMatchedObserverActions(event);for (ObserverAction observerAction : observerActions) {executor.execute(new Runnable() {@Overridepublic void run() {observerAction.execute(event);}});}}
}/*** @Description: 异步非阻塞* * @Author HJW* @Date 2021/4/24* @Version V1.0**/
public class AsyncEventBus extends EventBus{public AsyncEventBus(Executor executor) {super(executor);}
}​

客户端测试:

/*** @Description: TODO* @Author HJW* @Date 2021/4/24* @Version V1.0**/
public class Client {private static EventBus eventBus;private static final int DEFAULT_EVENTBUS_THREAD_POOL_SIZE = 3;public static void main(String[] args) {eventBus = new AsyncEventBus(newFixedThreadPool(DEFAULT_EVENTBUS_THREAD_POOL_SIZE)); // 异步非阻塞模式eventBus.register(new AObserver());eventBus.register(new BObserver());eventBus.register(new ZObserver());Messge messgeX = new XMsg("科技类", "华为终端造车");Messge messgeY = new YMsg("娱乐类", "xxx与xxx离婚");Messge messgeZ = new ZMsg("国际类", "印度新冠肆虐");eventBus.post(messgeX);eventBus.post(messgeY);eventBus.post(messgeZ); }}

四、观察者模式适合应用场景

  • 当一个对象状态的改变需要改变其他对象, 或实际对象是事先未知的或动态变化的时, 可使用观察者模式。

当你使用图形用户界面类时通常会遇到一个问题。 比如, 你创建了自定义按钮类并允许客户端在按钮中注入自定义代码, 这样当用户按下按钮时就会触发这些代码。

观察者模式允许任何实现了订阅者接口的对象订阅发布者对象的事件通知。 你可在按钮中添加订阅机制, 允许客户端通过自定义订阅类注入自定义代码。

  • 当应用中的一些对象必须观察其他对象时, 可使用该模式。 但仅能在有限时间内或特定情况下使用。

订阅列表是动态的, 因此订阅者可随时加入或离开该列表。

五、观察者模式优缺点

  • 开闭原则。 你无需修改发布者代码就能引入新的订阅者类 (如果是发布者接口则可轻松引入发布者类)。
  • 你可以在运行时建立对象之间的联系。
  • XXX 订阅者的通知顺序是随机的。

六、与其他模式的关系

  • 责任链模式、 命令模式、 中介者模式和观察者模式用于处理请求发送者和接收者之间的不同连接方式:

    • 责任链按照顺序将请求动态传递给一系列的潜在接收者, 直至其中一名接收者对请求进行处理。
    • 命令在发送者和请求者之间建立单向连接。
    • 中介者清除了发送者和请求者之间的直接连接, 强制它们通过一个中介对象进行间接沟通。
    • 观察者允许接收者动态地订阅或取消接收请求。
  • 中介者和观察者之间的区别往往很难记住。 在大部分情况下, 你可以使用其中一种模式, 而有时可以同时使用。 让我们来看看如何做到这一点。

    中介者的主要目标是消除一系列系统组件之间的相互依赖。 这些组件将依赖于同一个中介者对象。 观察者的目标是在对象之间建立动态的单向连接, 使得部分对象可作为其他对象的附属发挥作用。

    有一种流行的中介者模式实现方式依赖于观察者。 中介者对象担当发布者的角色, 其他组件则作为订阅者, 可以订阅中介者的事件或取消订阅。 当中介者以这种方式实现时, 它可能看上去与观察者非常相似。

    当你感到疑惑时, 记住可以采用其他方式来实现中介者。 例如, 你可永久性地将所有组件链接到同一个中介者对象。 这种实现方式和观察者并不相同, 但这仍是一种中介者模式。

    假设有一个程序, 其所有的组件都变成了发布者, 它们之间可以相互建立动态连接。 这样程序中就没有中心化的中介者对象, 而只有一些分布式的观察者。

设计模式之十三观察者模式相关推荐

  1. Java进阶篇设计模式之十三 ---- 观察者模式和空对象模式

    前言 在上一篇中我们学习了行为型模式的备忘录模式(Memento Pattern)和状态模式(Memento Pattern).本篇则来学习下行为型模式的最后两个模式,观察者模式(Observer P ...

  2. Java进阶篇设计模式之十三——观察者模式和空对象模式

    简介 观察者模式又叫发布-订阅(Publish/Subscribe)模式.模型-视图(Model/View)模式.源-监听器(Source/Listener)模式或从属者(Dependents)模式. ...

  3. java观察者模式异步notify_Java进阶篇设计模式之十三 ---- 观察者模式和空对象模式...

    前言 在上一篇中我们学习了行为型模式的备忘录模式(Memento Pattern)和状态模式(Memento Pattern).本篇则来学习下行为型模式的最后两个模式,观察者模式(Observer P ...

  4. 设计模式 ( 十六 ) 观察者模式Observer(对象行为型)

    设计模式 ( 十五 ) 观察者模式Observer(对象行为型) 1.概述 一些面向对象的编程方式,提供了一种构建对象间复杂网络互连的能力.当对象们连接在一起时,它们就可以相互提供服务和信息. 通常来 ...

  5. 外观模式 门面模式 Facade 结构型 设计模式(十三)

    外观模式(FACADE) 又称为门面模式 意图 为子系统中的一组接口提供一个一致的界面 Facade模式定义了一个高层接口,这一接口使得这一子系统更加易于使用. 意图解析 随着项目的持续发展,系统基本 ...

  6. 设计模式(四) Observe——观察者模式

    设计模式四 Observe--观察者模式 观察者模式 当对象间存在一对多关系时,则使用观察者模式(Observer Pattern).比如,当一个对象被修改时,则会自动通知它的依赖对象.观察者模式属于 ...

  7. 大聪明教你学Java设计模式 | 第十三篇:观察者模式

    前言

  8. 设计模式中的观察者模式

    观察者模式是一种软件设计模式,其中一个名为主体(Subject)的对象维护其依赖项列表,称为观察者,并通常通过调用它们(observers)的方法之一来自动通知它们任何状态更改. 观察者模式主要用于在 ...

  9. Java 设计模式之《观察者模式》

    很久之前,自己也曾看过一些设计模式的内容,最近在做一些程序代码设计的时,发现忘得差不多了,很多模式也只是有大致影响,决定重新将一些常用的模式复习一下.今天一个模式观察者模式. 观察者模式 观察者模式属 ...

最新文章

  1. java设计模式 观察者模式_理解java设计模式之观察者模式
  2. codevs1251 括号
  3. 使用 SetCustomizedString汉化UltraWinGrid的筛选
  4. Jenkins连接TFS出现错误:“jenkins com.microsoft.tfs.core.exceptions.TECoreException”的问题收集...
  5. cocos2d-x,求世界坐标
  6. mybatis-generator配置流程(详细) 2021-05-15
  7. Reporting service 技巧
  8. Linux 2.6内核Makefile浅析
  9. 广州客村计算机培训,愿达客村校区西班牙语培训班
  10. python爬取微信运动_用 Python 修改微信(支付宝)运动步数,轻松 TOP1
  11. Scrapy: 爬虫返回403错误
  12. Python数据分析学习 二
  13. python 二项分布_如何理解python中的二项分布?
  14. word流程图变为图片格式_图片如何转换成word?新手小白几步就学会了
  15. Python: 进行one-hot编码
  16. 工业大数据白皮书(2019版)
  17. 初次跑CNN进行掌纹识别遇到的问题
  18. SyntaxError: invalid character in identifier
  19. SV绿皮书笔记(九)暂时完结
  20. 全时定妆精致小欧眉,温柔与酷可以兼得

热门文章

  1. iOS 屏幕尺寸、分辨率、适配
  2. 特效行者app手机版制作飞天特效视频的教程
  3. java+http文件夹上传
  4. 微信怎么做小程序【做小程序】
  5. sprint 1 总结
  6. 兰博基尼lp650-4跑车介绍
  7. 【硬十宝典目录】——1、电源类(更新中~)
  8. 一个由两个长的如此相像的字引起的问题
  9. python3.6的新特性:f-strings格式化输出;python3.8新特性:f-strings增加了 = 说明符
  10. 蓝牙耳机哪种通话效果最好?通话质量最好的蓝牙耳机盘点