查看原文
其他

设计模式之观察者模式

MageByte技术团队 码哥字节 2022-10-28

设计模式之观察者模式

点击左上方公众号关注

观察者模式(有时又被称为模型(Model)-视图(View)模式、源-收听者(Listener)模式或从属者模式)是软件设计模式的一种。在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。

源代码地址:https://github.com/UniqueDong/zero-design-stu

主要角色

  • 主题接口 Subject:管理所有的观察者以及数据变化后通知观察者。

  • 观察者接口 Observer:接受自己订阅的主题发布的数据。

  • 主题实现类。

  • 观察者实现类。

使用场景

  • 报社的业务就是出版报纸,客户订阅该报社,那么只要有新的报纸出版就会给订阅报社的客户送来,只要一直是报社的订阅客户,就能一直收到新报纸。

  • 当你不想订阅,取消就可以,就不会再收到通知。

  • 报社提供订阅与取消订阅的入口。

实际上这里就是一个观察者模式的例子,报社充当 Subject 主题角色,订阅报社的客户就是 Observer 观察者角色。出版者-主题,订阅者-观察者。

代码实现

实现一

首先我们定义 Subject 主题角色报社 NewspaperSubject。主要提供 注册观察者、删除观察者、通知所有观察者方法。

定义包报纸对象 Newspaper

  1. public class Newspaper implements Serializable {

  2. private LocalDateTime reportTime;

  3. private String data;

  4. public LocalDateTime getReportTime() {

  5. return reportTime;

  6. }

  7. public void setReportTime(LocalDateTime reportTime) {

  8. this.reportTime = reportTime;

  9. }

  10. public String getData() {

  11. return data;

  12. }

  13. public void setData(String data) {

  14. this.data = data;

  15. }

  16. @Override

  17. public String toString() {

  18. return "Newspaper{" +

  19. "reportTime=" + reportTime +

  20. ", data='" + data + '\'' +

  21. '}';

  22. }

  23. }

定义主题对象

  1. public interface NewspaperSubject {

  2. /**

  3. * 注册观察者

  4. * @param observer

  5. */

  6. void registerObserver(Observer observer);

  7. /**

  8. * 移除观察者

  9. * @param observer

  10. */

  11. void removeObserver(Observer observer);

  12. /**

  13. * 通知所有观察者

  14. * @param data

  15. */

  16. void notifyObservers(Newspaper data);

  17. }

同时实现该主题,代码如下:

  1. import java.time.LocalDateTime;

  2. import java.util.ArrayList;

  3. import java.util.List;

  4. import java.util.concurrent.locks.ReentrantReadWriteLock;

  5. public class ChinaNewspaperSubject implements NewspaperSubject {

  6. private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

  7. private List<Observer> observers;

  8. public ChinaNewspaperSubject() {

  9. this.observers = new ArrayList<>();

  10. }

  11. public void setChange() {

  12. Newspaper newspaper = new Newspaper();

  13. newspaper.setReportTime(LocalDateTime.now());

  14. newspaper.setData("发布新闻");

  15. notifyObservers(newspaper);

  16. }

  17. @Override

  18. public void registerObserver(Observer observer) {

  19. ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();

  20. try {

  21. writeLock.lock();

  22. observers.add(observer);

  23. } finally {

  24. writeLock.unlock();

  25. }

  26. }

  27. @Override

  28. public void removeObserver(Observer observer) {

  29. ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();

  30. try {

  31. writeLock.lock();

  32. observers.remove(observer);

  33. } finally {

  34. writeLock.unlock();

  35. }

  36. }

  37. @Override

  38. public void notifyObservers(Newspaper data) {

  39. ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();

  40. try {

  41. readLock.lock();

  42. observers.forEach(item -> item.notice(data));

  43. } finally {

  44. readLock.unlock();

  45. }

  46. }

  47. }

然后定义观察者(Observer)角色 也就是报纸订阅者

  1. public interface Observer {

  2. /**

  3. * 接收主题发布的更新通知

  4. */

  5. void notice(Newspaper data);

  6. }

定义观察者具体实现类:一个香港用户订阅报纸

  1. public class HonKongObserver implements Observer {

  2. @Override

  3. public void notice(Newspaper data) {

  4. System.out.println("我收到报社的报纸了:" + "内容是" + data.toString());

  5. }

  6. }

最后测试

  1. public class Test {

  2. public static void main(String[] args) {

  3. //创建报社

  4. ChinaNewspaperSubject newspaperSubject = new ChinaNewspaperSubject();

  5. //创建订阅者

  6. HonKongObserver honKongObserver = new HonKongObserver();

  7. //订阅者关注该报社

  8. newspaperSubject.registerObserver(honKongObserver);

  9. //报社发布新报纸。所有逇订阅者收到报纸

  10. newspaperSubject.setChange();

  11. }

  12. }

方式二:通过JDK内置的实现

我们的JDK内部与为我们实现了观察者模式。只不过我们的主题需要继承 jdk 中的主题,观察者实现对应的Observer 接口。之前我们说过要多用组合与委托。面向接口编程而不是实现。内置的主题我们必须继承,若想更灵活其实我们自己定义主题接口会更好,并且也并不难。

首先我们的主题要先继承 Observerble ,这是jdk内置的。

  1. public class NumsObservable extends Observable {

  2. public final static Integer ODD = 1;

  3. public final static Integer EVEN = 2;

  4. private int data = 0;

  5. /**

  6. * 获取对象数据

  7. *

  8. * @return

  9. */

  10. public int getData() {

  11. return data;

  12. }

  13. /**

  14. * 设置数据变化

  15. * 根据数据的变化设置相应的标志变量,通知给订阅者

  16. *

  17. * @param data

  18. */

  19. public void setData(int data) {

  20. this.data = data;

  21. Integer flag = EVEN;

  22. if ((this.data & 0x0001) == 1) {

  23. flag = ODD;

  24. }

  25. setChanged();

  26. // 将变化的变化的标识变量通知给订阅者

  27. notifyObservers(flag);

  28. }

  29. }

接着定义我们的观察者:分别是偶数与奇数订阅者。

  1. /**

  2. * 奇数内容订阅类

  3. * Created by jianqing.li on 2017/6/8.

  4. */

  5. public class OddObserver implements Observer {

  6. /**

  7. * 继承自Observer接口类,update的方法的实现

  8. *

  9. * @param o 主题对象

  10. * @param arg notifyObservers(flag);传来的参数,即是标识变量

  11. */

  12. @Override

  13. public void update(Observable o, Object arg) {

  14. if (arg == NumsObservable.ODD) {

  15. NumsObservable numsObservable = (NumsObservable) o;

  16. System.out.println("Data has changed to ODD number " + numsObservable.getData());

  17. }

  18. }

  19. }

  1. /**

  2. * 偶数内容订阅类:订阅主题的内容的偶数变化

  3. * Created by jianqing.li on 2017/6/8.

  4. */

  5. public class EvenObserver implements Observer {

  6. /**

  7. * 继承自Observer接口类,update的方法的实现

  8. *

  9. * @param o 主题对象

  10. * @param arg notifyObservers(flag);传来的参数,即是标识变量

  11. */

  12. @Override

  13. public void update(Observable o, Object arg) {

  14. if (arg == NumsObservable.EVEN) {

  15. NumsObservable numsObservable = (NumsObservable) o;

  16. System.out.println("Data has changed to EVEN number " + numsObservable.getData());

  17. }

  18. }

  19. }

编写测试

  1. public class ObserverTest {

  2. public static void main(String[] args) {

  3. // 创建主题

  4. NumsObservable numsObservable = new NumsObservable();

  5. //创建订阅者

  6. OddObserver oddObserver = new OddObserver();

  7. EvenObserver evenObserver = new EvenObserver();

  8. numsObservable.addObserver(oddObserver);

  9. numsObservable.addObserver(evenObserver);

  10. //修改主题内容,触发notifyObservers

  11. numsObservable.setData(11);

  12. numsObservable.setData(12);

  13. numsObservable.setData(13);

  14. }

  15. }

总结

  • java.util.Observable 的阴暗面:它是一个类,我们的主题必须继承它,我们若是想继承其他类就无能为力了。毕竟 Java 不能多重继承。

  • 在哪里有观察者模式的运用?嘿嘿谷歌的 Guava 类库中的 EventBus 事件总线使用的就是观察者模式。

  • Spring 中的 事件传播 也是如此。

  • 主要作用就是为交互对象之间的松耦合。当一个对象改变,依赖它的对象都会收到通知。主题并不知道观察者的细节,只知道观察者实现了 Observer 接口。

客官觉得不过可以订阅 公众号 JavaStorm 与点赞,你的订阅就是最好的观察者应用。


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存