初学 Java 设计模式(十九):实战观察者模式 「您的快递已到达蜂站,请及时签收」

易羽fxst

一、观察者模式介绍

1. 解决的问题

主要解决将一个对象的状态改变通知给其他对象的问题。

2. 定义

观察者模式是一种行为设计模式,允许你定义一种订阅机制,可在对象事件发生时通知多个 “观察” 该对象的其他对象。

3. 应用场景

  • 当一个对象的改变需要改变其他对象,或实际对象时是事先未知的或动态变化时,可使用观察者模式。
  • 当应用中的一些对象必须观察其他对象时,可使用观察者模式,但仅能在有限时间内或特定情况下使用。

二、观察者模式优缺点

1. 优点

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

2. 缺点

  • 订阅者的通知顺序是随机的。

三、观察者模式应用实例:您的快递已到达蜂站,请及时签收

1. 实例场景

现在大部分快递都不会交付到手上了,除非我们特意选了上门服务。

一般都是买完两三天,收到条短信:您好,您的快递已到蜂站,请凭取件码K16888取件,取件时间09:30-21:00,请及时取件。

刚看完短信,APP也跳出一则通知:您的快递已被蜂站签收,请及时取件。

今天,就以快递到达,同时收到APP和短信通知为例,介绍一下观察者模式。

2. 观察者模式实现

2.1 工程结构
observer-pattern
└─ src
    ├─ main
    │    └─ java
    │    └─ org.design.pattern.observer
    │       ├─ model
    │       │    ├─ ExpressPackage.java
    │       │    ├─ Courier.java
    │       │    └─ User.java
    │       ├─ subscriber
    │       │    ├─ ExpressEventSubscriber.java
    │       │    └─ impl
    │       │        ├─ AppExpressEventListener.java
    │       │        └─ SmsExpressEventSubscriber.java
    │       ├─ publisher
    │       │    └─ ExpressEventPublisher.java
    │       └─ service
    │            ├─ ExpressService.java
    │            └─ impl
    │                 └─ ExpressServiceImpl.java
    └─ test
        └─ java
            └─ org.design.pattern.observer.test
                  └─ ExpressEventTest.java
2.2 代码实现
2.2.1 实体类

快递实体类

/**
 * 快递
 */
@Getter
@Setter
@AllArgsConstructor
public class Express {

    /**
     * 快递id
     */
    private String id;

    /**
     * 收件人名称
     */
    private String recipientName;

    /**
     * 收件人打电话
     */
    private String recipientPhone;

    /**
     * 收件人地址
     */
    private String recipientAddress;
}
2.2.2 发布者

快递事件发布者

/**
 * 快递事件发布者
 */
public class ExpressEventPublisher {

    /**
     * 快递事件订阅者列表
     */
    private List<ExpressEventSubscriber> subscriberList = new ArrayList<>();

    /**
     * 订阅快递事件
     *
     * @param expressEventSubscriber 订阅者
     */
    public void subscribe(ExpressEventSubscriber expressEventSubscriber) {
        subscriberList.add(expressEventSubscriber);
    }

    /**
     * 取消订阅快递事件
     *
     * @param expressEventSubscriber 订阅者
     */
    public void unsubscribe(ExpressEventSubscriber expressEventSubscriber) {
        subscriberList.remove(expressEventSubscriber);
    }

    /**
     * 通知订阅者快递到达
     *
     * @param express 快递
     */
    public void notifySubscribersArrive(Express express) {
        subscriberList.forEach(expressEventSubscriber -> expressEventSubscriber.arrive(express));
    }
}
2.2.3 订阅者

快递事件订阅者接口

/**
 * 快递事件订阅者接口
 */
public interface ExpressEventSubscriber {

    /**
     * 快递到达
     *
     * @param express 快递
     */
    void arrive(Express express);
}

App-快递事件订阅者

/**
 * App-快递事件订阅者
 */
@Slf4j
public class AppExpressEventListener implements ExpressEventSubscriber {

    /**
     * 快递到达
     *
     * @param express 快递
     */
    @Override
    public void arrive(Express express) {
        log.info("App 通知:您的包裹{}已达到蜂站!", express.getId());
    }
}

短信-快递事件订阅者

/**
 * 短信-快递事件订阅者
 */
@Slf4j
public class SmsExpressEventSubscriber implements ExpressEventSubscriber {
    /**
     * 快递到达
     *
     * @param express 快递
     */
    @Override
    public void arrive(Express express) {
        log.info("短信通知:您的包裹{}已达到蜂站!", express.getId());
    }
}
2.2.4 服务类

快递服务接口

/**
 * 快递服务接口
 */
public interface ExpressService {

    /**
     * 快递已到达
     *
     * @param express 快递
     */
    void arrive(Express express);
}

快递服务实现类

/**
 * 快递服务实现类
 */
@Slf4j
public class ExpressServiceImpl implements ExpressService {

    /**
     * 快递事件发布者
     */
    private final ExpressEventPublisher expressEventPublisher;

    /**
     * 构造方法
     *
     * @param expressEventPublisher 快递事件发布者
     */
    public ExpressServiceImpl(ExpressEventPublisher expressEventPublisher) {
        this.expressEventPublisher = expressEventPublisher;
    }

    /**
     * 快递到达
     *
     * @param express 快递
     */
    @Override
    public void arrive(Express express) {
        log.info("{}的快递已到达蜂站!", express.getRecipientName());
        expressEventPublisher.notifySubscribersArrive(express);
    }
}
2.3 测试验证
2.3.1 测试验证类
/**
 * 快递事件测试类
 */
public class ExpressEventTest {

    @Test
    public void test() {
        // 快递初始化
        Express express = new Express("1", "yiyufxst", "13245678910", "xx路");
        // 订阅者初始化
        ExpressEventSubscriber appExpressEventListener = new AppExpressEventListener();
        ExpressEventSubscriber smsExpressEventSubscriber = new SmsExpressEventSubscriber();
        // 发布者初始化
        ExpressEventPublisher expressEventPublisher = new ExpressEventPublisher();
        // 订阅快递事件
        expressEventPublisher.subscribe(appExpressEventListener);
        expressEventPublisher.subscribe(smsExpressEventSubscriber);
        // 快递服务类初始化
        ExpressService expressService = new ExpressServiceImpl(expressEventPublisher);
        // 快递到达
        expressService.arrive(express);
        // App取消订阅快递事件
        expressEventPublisher.unsubscribe(appExpressEventListener);
        // 模拟新快递
        express.setId("2");
        // 快递到达
        expressService.arrive(express);
    }
}
2.3.2 测试结果
14:50:01.090 [main] INFO  o.d.p.o.s.impl.ExpressServiceImpl - yiyufxst的快递已到达蜂站!
14:50:01.093 [main] INFO  o.d.p.o.s.i.AppExpressEventListener - App 通知:您的包裹1已达到蜂站!
14:50:01.093 [main] INFO  o.d.p.o.s.i.SmsExpressEventSubscriber - 短信通知:您的包裹1已达到蜂站!
14:50:01.093 [main] INFO  o.d.p.o.s.impl.ExpressServiceImpl - yiyufxst的快递已到达蜂站!
14:50:01.093 [main] INFO  o.d.p.o.s.i.SmsExpressEventSubscriber - 短信通知:您的包裹2已达到蜂站!

Process finished with exit code 0

四、观察者模式结构

观察者模式-模式结构图

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

    当新事件发生时,发送者会便利订阅者列表并调用每个订阅者对象的通知方法。

  2. 订阅者(Subscriber)接口声明了通知接口。在绝大多数情况下,该接口仅包含一个更新方法。该方法可以拥有多个参数,以便发布者能在更新时传递时间的详细信息。
  3. 具体订阅者(Concrete Subscribers)可以执行一些操作来回应发布者的通知。所有订阅者类实现了同样的接口,因此发布者无需与具体类耦合。

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

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

设计模式并不难学,其本身就是多年经验提炼出的开发指导思想,关键在于多加练习,带着使用设计模式的思想去优化代码,就能构建出更合理的代码。

源码地址:https://github.com/yiyufxst/design-pattern-java

参考资料:
小博哥重学设计模式:https://github.com/fuzhengwei/itstack-demo-design
深入设计模式:https://refactoringguru.cn/design-patterns/catalog

阅读 221

学习笔记
个人学习笔记
158 声望
3 粉丝
0 条评论
你知道吗?

158 声望
3 粉丝
宣传栏