设计模式-观察者模式

发布于:2025-06-11 ⋅ 阅读:(19) ⋅ 点赞:(0)

写在前面

Hello,我是易元,这篇文章是我学习设计模式时的笔记和心得体会。如果其中有错误,欢迎大家留言指正!


需求背景

小区物业管理作为社区生活的重要保障,现有依托实体公告栏的信息发布模式存在服务盲区:缴费通知、公共事务表决、设施停运预警等时效性信息受限于物理覆盖范围和更新频次,导致居民获取信息滞后、关键事项遗漏的情况频发,进而引发服务纠纷与投诉。 物业张经理希望能够开发一个智慧社区信息发布系统,满足以下需求:

  1. 物业可以发布不同类型的通知(如紧急通知、活动通知、缴费通知等)

  2. 居民可以选择接收哪些类型的通知

  3. 可以有多种不同的方式接收消息(如短信、邮件、APP推送等)

常规代码实现

根据初步需求,小易开始着手开发社区通知系统,当物业需要发布通知时,系统将自动向所有注册的居民发送消息。 首先我们先尝试使用比较直接、常规的方式进行实现。这个阶段,暂时不考虑引入任何特定的设计模式,而是专注于功能的快速实现。

1.居民类

public class Resident {

    private String id;

    private String name;

    private String phoneNumber;

    private String email;

    public Resident(String id, String name, String phoneNumber, String email) {
        this.id = id;
        this.name = name;
        this.phoneNumber = phoneNumber;
        this.email = email;
    }
// 省略 get set 方法
}

2.通知类

public class Notification {

    private String id;

    private String title;

    private String content;

    private LocalDateTime createTime;

    public Notification(String id, String title, String content) {
        this.id = id;
        this.title = title;
        this.content = content;
        this.createTime = LocalDateTime.now();
    }

// 省略 get set 方法
}

3.通知服务类

public class NotificationService {

    private List<Resident> residents = new ArrayList<>();

    public void registerResident(Resident resident) {
        residents.add(resident);
        System.out.println(String.format("居民 %s 已注册到通知系统", resident.getName()));
    }

    public void removeResident(Resident resident) {
        residents.remove(resident);
        System.out.println(String.format("居民 %s 已从通知系统移除", resident.getName()));
    }

    public void sendNotification(Notification notification) {
        System.out.println(String.format("开始发送通知: %s", notification.getTitle()));

        for (Resident resident : residents) {
            this.sendSMS(resident, notification);

            this.sendEmail(resident, notification);

            System.out.println();
        }

        System.out.println("通知发送完成");
    }

    private void sendSMS(Resident resident, Notification notification) {
        System.out.println(
                String.format(
                        "发送短信给%s(%s): %s-%s",
                        resident.getName(),
                        resident.getPhoneNumber(),
                        notification.getTitle(),
                        notification.getContent()
                )
        );
    }

    private void sendEmail(Resident resident, Notification notification) {
        System.out.println(
                String.format(
                        "发送邮件给%s(%s): %s-%s",
                        resident.getName(),
                        resident.getEmail(),
                        notification.getTitle(),
                        notification.getContent()
                )
        );
    }
}

4.物业管理类

public class PropertyManagement {

    private NotificationService notificationService;

    public PropertyManagement() {
        this.notificationService = new NotificationService();
    }

    /**
     * 注册居民
     *
     * @param resident
     */
    public void registerResident(Resident resident) {
        notificationService.registerResident(resident);
    }

    /**
     * 移除居民
     *
     * @param resident
     */
    public void removeResident(Resident resident) {
        notificationService.removeResident(resident);
    }

    /**
     * 发布通知
     *
     * @param notification
     */
    public void publishNotification(Notification notification) {
        System.out.println(String.format("物业发布新通知: %s", notification.getTitle()));
        notificationService.sendNotification(notification);
    }

    /**
     * 获取通知服务
     *
     * @return NotificationService
     */
    public NotificationService getNotificationService() {
        return notificationService;
    }
}
代码说明
  1. Resident: 居民类,存储居民的基本信息。

  2. Notification: 通知类,表示物业发布的通知信息。

  3. NotificationService: 负责物业管理中的居民通知服务,主要实现居民信息管理及多通道通知发送功能
    • resident:类型为 List<Resident>,用于存储所有注册到通知系统的居民信息。

    • registerResident(Resident resident):将居民对象添加到 residents 列表中。

    • registerResident(Resident resident):将居民对象在 residents 列表中移除。

    • sendNotification(Notification notification):遍历 residents 列表中已经注册的居民,并调用 sendSMS() 发送短信通知,调用 sendEmail() 发送邮件通知。

    • sendSMS()/sendEmail():模拟发送对应消息。

  4. PropertyManagement: 作为物业管理系统的核心控制类,主要处理居民信息管理与通知发布功能
    • notificationService:私有通知服务实例,负责具体居民的注册及通知分发。

    • registerResident(Resident resident)/removeResident(Resident resident):委托 notificationService 实现居民的注册,移除。

    • publishNotification(Notification notification):发布通知,调用 notificationService.sendNotification(notification),触发通知发送给已经注册的居民。

    • getNotificationService():返回内部的 notificationService 实例,允许外部获取通知服务对象进行扩展操作(如查询发送状态)。

常规实现的问题分析

在常规代码实现中,通知的方式是硬编码在 NotificationService 类中,导致以下问题:

  1. 居民每次都同时收到短信和邮件,无法根据偏好选择通知方式。

  2. 若要增加新的通知方式(如APP推送、微信公众号等),需要修改 NotificationService 代码,违反开闭原则。

  3. 居民无法实现自定义配置,所有通知、所有渠道均要发送。

观察者模式

1.基本概念

观察者模式(Observer Pattern)是一种行为型设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,当主题对象状态发生变化时,它所依赖者(观察者)都会收到通知并自动更新。

2.核心组成部分

  • 被观察者(Subject):维护一系列观察者,并提供添加、删除和通知观察者的方法。

  • 观察者(Observer):定义一个更新接口,用于接收被观察者的通知。

  • 具体被观察者(ConcreteSubject):实现被观察者接口,并在状态变化时通知所有已经注册的观察者。

  • 具体观察者(ConcreteObserver):实现观察者接口,并在收到通知时更新自身状态。

3.工作流程

  1. 被观察者维护一个观察者的列表。

  2. 当被观察者状态发生变化时,它会遍历观察者列表,调用每个观察者的更新方法。

  3. 观察者收到通知后,可以访问被观察者的状态并进行相应的处理。

这种工作流程使得被观察者和观察者之间的耦合度降低,被观察者不需要知道观察者的具体实现,只需要知道观察者实现了特定接口。

4.适用场景

  1. 当一个对象的状态变化需要通知其他对象,而又不希望这些对象与该对象紧密耦合时。 例如:社区通知系统,通知发布后需要通过多种渠道发送,但不希望通知发布系统与具体通知方式紧密耦合。

  2. 当一个对象状态的改变需要改变其他对象,或实际对象是事先未知的或动态变化的时候,可以使用观察者模式。 当使用图形用户界面类时通常会遇到一个问题,比如:你创建了自定义按钮类并允许客户端在按钮中注入自定义代码,这样当用户按下按钮时,就会触发这些代码 观察者模式允许任何实现了订阅者接口的对象订阅发布者对象的事件通知,你可在按钮中添加订阅机制,允许客户端通过自定义订阅类注入自定义代码。

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

  4. 当一个对象的变化会导致一系列其他对象的变化,但不知道具体会有多少对象变化时。

  5. 当一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两者封装在独立的对象中可以使它们各自独立地改变和复用。

  6. 当需要在系统中创建一种触发机制,使得某些事件发生时,触发一系列连锁反应。

观察者重构通知系统

1. 重构后的系统架构

  1. 被观察者(Subject)
    1. 定义通知发布者的接口

    2. 提供注册和移除观察者的方法

    3. 提供通知观察者的方法

  2. 具体被观察者 (Concrete Subject)
    1. 实现通知发布者的接口

    2. 维护观察者列表

    3. 在状态变化时通知观察者

  3. 观察者 (Observer)
    1. 定义通知接收者的接口;

    2. 提供更新的方法,用于接收通知。

  4. 具体观察者 (Concrete Observer)
    1. 实现观察者接口

    2. 实现特定的通知处理逻辑。

  5. 通知类型 (Notification Type)
    1. 定义不同类型的通知

    2. 允许观察者根据通知类型进行过滤

  6. 居民偏好 (Resident Preference)
    1. 存储居民的通知偏好

    2. 决定居民接收哪些类型的通知,以及通过什么方式接收。

2. 重构后代码

通知类型枚举
public enum NotificationType {

    EMERGENCY("紧急通知"),
    ACTIVITY("活动通知"),
    PAYMENT("缴费通知"),
    MAINTENANCE("维修通知"),
    ;

    private String description;

    NotificationType(String description) {
        this.description = description;
    }

    public String getDescription() {
        return description;
    }
}
通知类
public class Notification {

    private String id;

    private String title;

    private String content;

    private NotificationType type;

    private LocalDateTime createTime;

    public Notification() {
    }

    public Notification(String id, String title, String content, NotificationType type, LocalDateTime createTime) {
        this.id = id;
        this.title = title;
        this.content = content;
        this.type = type;
        this.createTime = createTime;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public NotificationType getType() {
        return type;
    }

    public void setType(NotificationType type) {
        this.type = type;
    }

    public LocalDateTime getCreateTime() {
        return createTime;
    }

    public void setCreateTime(LocalDateTime createTime) {
        this.createTime = createTime;
    }

    @Override
    public String toString() {
        return "Notification{" +
                "id='" + id + '\'' +
                ", title='" + title + '\'' +
                ", content='" + content + '\'' +
                ", type=" + type +
                ", createTime=" + createTime +
                '}';
    }
}
居民类
public class Resident {

    private String id;

    private String name;

    private String phoneNumber;

    private String email;

    private String appUserId;

    private Map<NotificationType, Set<NotificationMethod>> preferences = new HashMap<>();

    public Resident(String id, String name, String phoneNumber, String email) {
        this.id = id;
        this.name = name;
        this.phoneNumber = phoneNumber;
        this.email = email;

        for (NotificationType type : NotificationType.values()) {

            preferences.put(type, new HashSet<>());
            preferences.get(type).add(NotificationMethod.SMS);
            preferences.get(type).add(NotificationMethod.EMAIL);
            preferences.get(type).add(NotificationMethod.WECHAT);

        }
    }

    public Resident(String id, String name, String phoneNumber, String email, String appUserId) {
        this(id, name, phoneNumber, email);
        this.appUserId = appUserId;

        for (NotificationType type : NotificationType.values()) {
            preferences.get(type).add(NotificationMethod.APP);
        }
    }

    public String getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public String getEmail() {
        return email;
    }

    public String getAppUserId() {
        return appUserId;
    }

    public void setAppUserId(String appUserId) {
        this.appUserId = appUserId;
    }

    /**
     * 设置通知偏好
     *
     * @param type
     * @param methods
     */
    public void setPreference(NotificationType type, Set<NotificationMethod> methods) {
        preferences.put(type, new HashSet<>(methods));
    }

    /**
     * 添加通知偏好
     *
     * @param type
     * @param method
     */
    public void addPreference(NotificationType type, NotificationMethod method) {
        if (!preferences.containsKey(type)) {
            preferences.put(type, new HashSet<>());
        }

        preferences.get(type).add(method);
    }

    /**
     * 移除通知偏好
     *
     * @param type
     * @param method
     */
    public void removePreference(NotificationType type, NotificationMethod method) {
        if (preferences.containsKey(type)) {
            preferences.get(type).remove(method);
        }
    }

    /**
     * 通过特定方式,检查是否接收特定类型的通知
     *
     * @param type
     * @param method
     * @return
     */
    public boolean hasPreference(NotificationType type, NotificationMethod method) {
        return preferences.containsKey(type) && preferences.get(type).contains(method);
    }

    /**
     * 获取特定类型通知的所有接收方式
     *
     * @param type
     * @return
     */
    public Set<NotificationMethod> getMethodsForType(NotificationType type) {
        return preferences.getOrDefault(type, new HashSet<>());
    }

    @Override
    public String toString() {
        return "Resident{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", phoneNumber='" + phoneNumber + '\'' +
                ", email='" + email + '\'' +
                ", appUserId='" + appUserId + '\'' +
                ", preferences=" + preferences +
                '}';
    }
}

通知方式枚举
public enum NotificationMethod {

    SMS("短信"),
    EMAIL("邮件"),
    APP("APP推送"),
    WECHAT("微信公众号"),

    ;

    private String description;

    NotificationMethod(String description) {
        this.description = description;
    }

    public String getDescription() {
        return description;
    }
}
观察者接口
public interface NotificationObserver {

    /**
     * 有新通知时,调用此方法
     *
     * @param notification
     * @param resident
     */
    void update(Notification notification, Resident resident);

    /**
     * 获取通知方式
     *
     * @return
     */
    NotificationMethod getMethod();
}
具体观察者
/**
 * WeChat观察者
 */
public class WeChatNotificationObserver implements NotificationObserver {

    @Override
    public void update(Notification notification, Resident resident) {
        System.out.println(
                String.format(
                        "发送微信公众号推送给 %s:%s-%s-%s",
                        resident.getName(),
                        notification.getType().getDescription(),
                        notification.getTitle(),
                        notification.getContent()
                )
        );
    }

    @Override
    public NotificationMethod getMethod() {
        return NotificationMethod.WECHAT;
    }
}

/**
 * SMS 观察者
 */
public class SMSNotificationObserver implements NotificationObserver {

    @Override
    public void update(Notification notification, Resident resident) {

        System.out.println(
                String.format(
                        "发送短信给%s(%s):%s-%s-%s",
                        resident.getName(),
                        resident.getPhoneNumber(),
                        notification.getType().getDescription(),
                        notification.getTitle(),
                        notification.getContent()
                )
        );

    }

    @Override
    public NotificationMethod getMethod() {
        return NotificationMethod.SMS;
    }
}


/**
 * Email观察者
 */
public class EmailNotificationObserver implements NotificationObserver {

    @Override
    public void update(Notification notification, Resident resident) {
        System.out.println(
                String.format(
                        "发送邮件给%s(%s):%s-%s-%s",
                        resident.getName(),
                        resident.getEmail(),
                        notification.getType().getDescription(),
                        notification.getTitle(),
                        notification.getContent()
                )
        );
    }

    @Override
    public NotificationMethod getMethod() {
        return NotificationMethod.EMAIL;
    }
}

/**
 * APP 观察者
 */
public class AppNotificationObserver implements NotificationObserver {

    @Override
    public void update(Notification notification, Resident resident) {
        if (resident.getAppUserId() == null || resident.getAppUserId().isEmpty()) {
            System.out.println(String.format("居民%s没有APP账号,无法发送APP推送", resident.getName()));
            return;
        }

        System.out.println(
                String.format(
                        "发送APP推送给%s(APP用户ID:%s):%s-%s-%s",
                        resident.getName(),
                        resident.getAppUserId(),
                        notification.getType().getDescription(),
                        notification.getTitle(),
                        notification.getContent()
                )
        );
    }

    @Override
    public NotificationMethod getMethod() {
        return NotificationMethod.APP;
    }
}

被观察者接口
public interface NotificationSubject {

    /**
     * 注册观察者
     *
     * @param observer
     */
    void registerObserver(NotificationObserver observer);

    /**
     * 移除观察者
     *
     * @param observer
     */
    void removeObserver(NotificationObserver observer);

    /**
     * 通知所有相关的观察者
     *
     * @param notification
     */
    void notifyObservers(Notification notification);

}

具体被观察者实现
public class NotificationPublisher implements NotificationSubject {

    // 存储所有观察者
    private List<NotificationObserver> observers = new ArrayList<>();

    // 存储所有注册的居民
    private List<Resident> residents = new ArrayList<>();

    private Map<NotificationMethod, NotificationObserver> methodObserverMap = new HashMap<>();

    @Override
    public void registerObserver(NotificationObserver observer) {

        if (!observers.contains(observer)) {
            observers.add(observer);
            methodObserverMap.put(observer.getMethod(), observer);
            System.out.println(String.format("注册观察者: %s", observer.getMethod().getDescription()));
        }

    }

    @Override
    public void removeObserver(NotificationObserver observer) {
        observers.remove(observer);
        methodObserverMap.remove(observer.getMethod());
        System.out.println(String.format("移除观察者: %s", observer.getMethod().getDescription()));
    }

    @Override
    public void notifyObservers(Notification notification) {

        System.out.println(String.format("开始发送通知: %s(%s)", notification.getTitle(), notification));

        for (Resident resident : residents) {

            for (NotificationMethod method : resident.getMethodsForType(notification.getType())) {

                NotificationObserver observer = methodObserverMap.get(method);

                if (observer != null) {
                    observer.update(notification, resident);
                }

            }

        }

        System.out.println("通知发送完成");
    }

    /**
     * 注册居民
     *
     * @param resident
     */
    public void registerResident(Resident resident) {
        if (!residents.contains(resident)) {
            residents.add(resident);
            System.out.println(String.format("居民 %s 已注册到通知系统", resident.getName()));
        }
    }

    /**
     * 移除居民
     *
     * @param resident
     */
    public void removeResident(Resident resident) {
        residents.remove(resident);
        System.out.println(String.format("居民 %s 已从通知系统移除", resident.getName()));
    }

    /**
     * 发布通知
     *
     * @param notification
     */
    public void publishNotification(Notification notification) {
        System.out.println(String.format("发布新通知: %s (%s)", notification.getTitle(), notification.getType().getDescription()));
        notifyObservers(notification);
    }

}

物业管理类
public class PropertyManagement {

    private NotificationPublisher notificationPublisher;

    public PropertyManagement() {

        this.notificationPublisher = new NotificationPublisher();

        // 注册所有通知方式
        notificationPublisher.registerObserver(new SMSNotificationObserver());
        notificationPublisher.registerObserver(new EmailNotificationObserver());
        notificationPublisher.registerObserver(new AppNotificationObserver());
        notificationPublisher.registerObserver(new WeChatNotificationObserver());

    }

    /**
     * 注册居民
     *
     * @param resident
     */
    public void registerResident(Resident resident) {
        notificationPublisher.registerResident(resident);
    }

    /**
     * 移除居民
     *
     * @param resident
     */
    public void removeResident(Resident resident) {
        notificationPublisher.removeResident(resident);
    }

    /**
     * 发布通知
     *
     * @param notification
     */
    public void publishNotification(Notification notification) {
        notificationPublisher.publishNotification(notification);
    }

    /**
     * 获取通知发布系统
     *
     * @return
     */
    public NotificationPublisher getNotificationPublisher() {
        return notificationPublisher;
    }

}

测试类
    @Test
    public void test_observerPattern() {
        PropertyManagement propertyManagement = new PropertyManagement();
        Resident resident1 = new Resident("1", "张三", "13800138001", "zhangsan@example.com", "app001");
        Resident resident2 = new Resident("2", "李四", "13800138002", "lisi@example.com", "app002");
        Resident resident3 = new Resident("3", "王五", "13800138003", "wangwu@example.com", "app003");

        propertyManagement.registerResident(resident1);
        propertyManagement.registerResident(resident2);
        propertyManagement.registerResident(resident3);

        System.out.println();
        System.out.println("----------------------------------------------------------");
        System.out.println();

        // 设置居民偏好
        // 张三接收所有类型通知

        // 李四 只接收紧急通知,通过微信渠道
        Set<NotificationMethod> methods = new HashSet<>();
        methods.add(NotificationMethod.SMS);
        resident2.setPreference(NotificationType.EMERGENCY, methods);
        resident2.setPreference(NotificationType.ACTIVITY, new HashSet<>());
        resident2.setPreference(NotificationType.PAYMENT, new HashSet<>());
        resident2.setPreference(NotificationType.MAINTENANCE, new HashSet<>());

        // 王五 接收紧急通知和活动通知,通过邮件和APP
        methods = new HashSet<>();
        methods.add(NotificationMethod.EMAIL);
        methods.add(NotificationMethod.APP);
        resident3.setPreference(NotificationType.EMERGENCY, methods);
        resident3.setPreference(NotificationType.ACTIVITY, methods);
        resident3.setPreference(NotificationType.PAYMENT, new HashSet<>());
        resident3.setPreference(NotificationType.MAINTENANCE, new HashSet<>());

        // 发布紧急通知
        Notification emergencyNotification =
                new Notification(
                        "N001",
                        "停水通知",
                        "因管道维修,小区将于明天上午9点至下午5点停水,请提前做好准备。",
                        NotificationType.EMERGENCY,
                        LocalDateTime.now());
        propertyManagement.publishNotification(emergencyNotification);

        System.out.println();
        System.out.println("----------------------------------------------------------");
        System.out.println();

        // 发布活动通知
        Notification activityNotification = new Notification(
                "N002",
                "小区活动通知",
                "本周六上午10点在小区广场举办迎新舂活动,欢迎大家参加。",
                NotificationType.ACTIVITY,
                LocalDateTime.now());
        propertyManagement.publishNotification(activityNotification);

    }

运行结果
---------------------------------------------

居民 张三 已注册到通知系统
居民 李四 已注册到通知系统
居民 王五 已注册到通知系统

---------------------------------------------

物业发布新通知: 停水通知
开始发送通知: 停水通知
发送短信给张三(13800138001): 停水通知-因管道维修,小区将于明天上午9点至5点停水,请提前做好准备。
发送邮件给张三(zhangsan@example.com): 停水通知-因管道维修,小区将于明天上午9点至5点停水,请提前做好准备。

发送短信给李四(13800138002): 停水通知-因管道维修,小区将于明天上午9点至5点停水,请提前做好准备。
发送邮件给李四(lisi@example.com): 停水通知-因管道维修,小区将于明天上午9点至5点停水,请提前做好准备。

发送短信给王五(13800138003): 停水通知-因管道维修,小区将于明天上午9点至5点停水,请提前做好准备。
发送邮件给王五(wangwu@example.com): 停水通知-因管道维修,小区将于明天上午9点至5点停水,请提前做好准备。

通知发送完成

---------------------------------------------

居民 李四 已从通知系统移除
物业发布新通知: 小区活动通知
开始发送通知: 小区活动通知
发送短信给张三(13800138001): 小区活动通知-本周六上午10点在小区广场举办迎新春活动,欢迎大家参加。
发送邮件给张三(zhangsan@example.com): 小区活动通知-本周六上午10点在小区广场举办迎新春活动,欢迎大家参加。

发送短信给王五(13800138003): 小区活动通知-本周六上午10点在小区广场举办迎新春活动,欢迎大家参加。
发送邮件给王五(wangwu@example.com): 小区活动通知-本周六上午10点在小区广场举办迎新春活动,欢迎大家参加。

通知发送完成

---------------------------------------------


Process finished with exit code 0

重构后的优势

  1. 松耦合的设计使得通知发布系统和通知方式直接的依赖降低,各部分可以独立开发,通知发布系统不需要知道具体的通知方式的实现细节,只需要知道他们实现了特定的接口。

  2. 符合开闭原则的设计,使得系统更容易扩展,可以在不修改原有代码的情况下,通过添加新的类来扩展系统功能,使得新功能的添加不会影响现有功能的稳定性。

  3. 支持个性化需求的设计,使得系统能够满足不同居民不同需求,提高用户体验,居民可以选择接收哪些类型的通知,以及通过上面方式接收,系统会根据居民的偏好,选择合适的观察者进行通知。

  4. 通知类型的区分与过滤使得系统能够根据通知类型进行不同的处理,未不同类型的通知设置不同的处理逻辑,为未来的通知类型扩展提供的基础。

观察者模式为社区通知系统提供了一个灵活、可扩展的架构,使得系统能够适应不断变化的需求,提供更好的用户体验。

长话短说

核心思想

观察者模式的核心思想可用一句话概括:当一个对象的状态发生变化时,所有依赖它的对象都会得到通知并执行特定逻辑。 这种设计模式简历了一种一对多的依赖关系,使得当一个对象的状态发生变化时,其所有依赖者都会收到通知,并作出相应逻辑。 从本质上讲,观察者模式是一种事件驱动的编程范式,将 变化 抽象为 事件,将 依赖关系 抽象为 订阅-发布机制。 这种抽象使得系统中的各个组件能够在保持孙耦合的同时进行有效的通信和协作。

观察者的核心思想主要体现在以下几个方面:

  1. 松耦合设计 观察者模式的最大优势在于实现了被观察者和观察者之间的松耦合。被观察者只需要维护一个观察者列表,不需要了解具体观察者的实现细节;观察者是需要实现统一接口,而不需要了解被观察者的内部结构。

  2. 开闭原则 观察者模式很好的体现了开闭原则:对扩展开发,对修改关闭。通过定义稳定的接口和抽象类,观察者模式使得系统在不修改现有代码的情况下,通过添加新的观察者来扩展功能;

  3. 事件驱动 观察者模式是事件驱动编程的典型实现,在事件驱动编程中,程序的执行流程不是由程序员预先设定的,而是由外部事件触发。

  4. 一对多关系 观察者模式建立了一种一对多的依赖关系:一个被观察者可以有多个观察者,当被观察者的状态发生变化时,所有观察者都会收到通知,这种一对多的关系使得系统能够是实现广播式的通信,一个状态变化可以触发多个操作。

适用场景

  1. 当一个对象的状态变化需要通知其他对象,而且这些对象的数量或身份可能会动态变化。 观察者模式最基本的使用场景,当一个对象的状态发生变化,需要通知其他对象,而这些对象可能随时会增加或减少,这是就可以使用观察者模式,例如:在通知系统中,发布系统的状态变化需要通知各种通知方式(短信、邮件、APP推送等),而这些通知方式可能会随时增加或减少,这正是观察者模式的典型应用场景。

  2. 当一个对象变化需要触发其他对象的连锁反应,但又不希望和这些对象之间产生强耦合。 在某些情况下,一个对象的变化需要触发其他对象的一系列操作,但这些对象之间不应该有直接的依赖关系,这时可以使用观察者模式来实现松耦合的连锁效应。例如:在电商系统中,当订单状态变为已支付时,需要触发库存减扣、物流发货、积分增加等一系列操作,但这些操作之间不应该有之间的依赖关系,可以使用观察者模式。

  3. 当一个对象的变化需要通知其他对象,但是不知道具体有多少对象需要被通知 一个对象的变化需要通知其他对象,但是具体有多少对象需要被通知是不确定的,这时可以使用观察者模式来实现动态的通知机制。

  4. 需要实现发布-订阅模式,使得发布者和订阅者之间解耦。 发布-订阅模式是观察者模式的一种变体,通过一个中间层(事件通道)来实现发布者和订阅者之间的完全解耦。

标准编写步骤

  1. 定义观察者接口 定义一个观察者接口,该接口包含一个更新方法,用于在被观察者状态变化的时候通知观察者。

public interface Observer{
 
 void update(Subject subject,Object arg);
}
  1. 定义被观察者接口或抽象类 定义一个被观察者接口或抽象类,该接口或抽象类包含添加观察者、删除观察者和通知观察者的方法。

public interface Subject{

 void addObserver(Observer observer);
 
 void removeObserver(Observer observer);
 
 void notifyObservers();
 
 void notifyObservers(Object arg);
}
  1. 实现具体的被观察者 实现具体的被观察者类,该类实现被观察者接口或者继承被观察者抽象类,并维护一个观察者列表。

public class ConcreteSubject implements Subject{
 private List<Observer> observers = new ArrayList();
 private Object state;
 
 @Override
 public void addObserver(Observer observer){
  if(!observers.contains(observer)){
   observers.add(observer);
  }
 }
 
 @Override
 public void removeObserver(Observer observer){
  observers.remove(observer);
 }
 
 @Override
 public void notifyObservers(){
  notifyObservers(null);
 }
 
 @Override
 public void notifyObservers(Object arg){
  for(Observer observer : observers){
   observer.update(this,arg);
  }
 }
 
 public Object getState(){
  return state;
 }
 
 public void setState(Object state){
  this.state = state;
  notifyObservers();
 }
}
  1. 实现具体的观察者 实现具体的观察者类,该类实现观察者接口,并在更新方法中定义具体的响应逻辑。

public class ConcreteObserver implements Observer{
 private Object observerState;
 
 @Override
 public void update(Subject subject, Object arg){
  if(subject interface ConcreteSubject){
   ConcreteSubject concreteSbject = (ConcreteSubject) subject;
   this.observerState = concreteSbject.getState();
   // 观察者状态变更通知
  }
 }
}
  1. 使用观察者模式 实际使用中,创建具体的被观察者和观察者对象,将观察者注册到被观察者中,然后改变被观察者的状态,触发通知。

ConcreteSubject subject = new ConcreteSubject();

Observer observer1 = new ConcreteObserver();
Observer observer2 = new ConcreteObserver();

subject.addObserver(observer1);
subject.addObserver(observer2);

subject.setState("新状态");

注意事项

  1. 避免循环依赖 在观察者模式中,如果观察者和被观察者之间存在循环依赖,可能会导致无限循环通知,进而系统崩溃。

  2. 考虑线程安全 在多线程环境下,观察者列表的添加、删除和遍历操作可能会发生并发问题,需要考虑线程安全。可以使用线程安全的集合或添加同步机制来解决。

3.注意内存泄漏 在使用观察者模式时,若观察者并没有正确的从被观察者中移除,可能会导致内存泄漏,特别是在使用匿名内部类或者Lambda表达式做为观察者时,更容易发生这种情况,因此,在观察者不再需要时,应及时从被观察者列表中移除。

4.控制通知频率 若被观察者的状态变化非常频繁,可能会导致观察者被频繁通知,可以考虑使用节流(Throttling)或防抖(Debouncing)技术来控制通知频率。

5.处理异常 在观察者的更新方法中,可能会发生异常,若不妥善处理,可能会影响其他观察者的正常工作。可以在被观察者的通知方法中添加异常处理机制,确保一个观察者的异常不会影响其他观察者。

变体和扩展

观察者模式有多种变体和扩展,可以根据具体需求调整和优化:

  1. 推模型和拉模型

  • 推模型:被观察者主动将所有相关数据推送给观察者,这种方式简单直接,但可能会推送观察者不需要的数据。

  • 拉模型:被观察者只通知观察者有状态变化,观察者根据需要主动获取所需数据。

  1. 同步通知和异步通知

  • 同步通知:被观察者在状态变化后,立即调用所有观察者的更新方法,等所有观察者处理完毕后才返回。

  • 异步通知:被观察者在状态变化后,将通知事件放入队列中,由专门的线程异步处理,不会阻塞主线程。

  1. 事件对象 在观察者模式中,可以引入事件对象来封装状态变化的详细信息,使得通知更加灵活和丰富。事件对象通常包含事件源、事件类型、事件数据等信息。

  2. 带优先级的观察者 在某些情况下,不同的观察者可能有不同的优先级,需要按照优先级顺序进行通知,可通过在被观察者中维护一个带优先级的观察者列表来实现。

  3. 带过滤器的观察者 在某些情况下,观察者可能只对特定类型的状态变化感兴趣,可以通过在被观察者中添加过滤器来实现,只有符合条件的状态变化才会通知相应的观察者。