行为型-观察者模式

1. 项目结构

  1. 项目结构
bash 复制代码
	observer-pattern-demo/
	├── pom.xml
	├── src/
	│   └── main/
	│       └── java/
	│           └── com/
	│               └── example/
	│                   ├── observer/
	│                   │   ├── NewsPublisher.java
	│                   │   ├── NewsSubscriber.java
	│                   │   ├── Subscriber.java
	│                   │   └── NewsType.java
	│                   └── Main.java
  1. Maven 配置文件 (pom.xml)

    xml 复制代码
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
             http://maven.apache.org/xsd/maven-4.0.0.xsd">
        
        <modelVersion>4.0.0</modelVersion>
        
        <groupId>com.example</groupId>
        <artifactId>observer-pattern-demo</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>jar</packaging>
        
        <name>Observer Pattern Demo</name>
        <description>Java Observer Pattern Implementation with Maven</description>
        
        <properties>
            <maven.compiler.source>1.8</maven.compiler.source>
            <maven.compiler.target>1.8</maven.compiler.target>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>
        
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.1</version>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </project>

2. 代码实现

  1. 新闻类型枚举(NewsType.java)

    java 复制代码
    package com.example.observer;
    
    /**
     * 新闻类型枚举
     */
    public enum NewsType {
        POLITICS("ZZ新闻"),
        SPORTS("TY新闻"),
        TECHNOLOGY("KJ新闻"),
        ENTERTAINMENT("YL新闻"),
        BUSINESS("CJ新闻");
        
        private final String description;
        
        NewsType(String description) {
            this.description = description;
        }
        
        public String getDescription() {
            return description;
        }
    }
  2. 订阅者接口 (Subscriber.java)

    java 复制代码
    package com.example.observer;
    
    /**
     * 订阅者接口(观察者接口)
     * 所有观察者都需要实现这个接口
     */
    public interface Subscriber {
        /**
         * 更新方法,当主题状态变化时被调用
         * @param newsType 新闻类型
         * @param newsContent 新闻内容
         */
        void update(NewsType newsType, String newsContent);
        
        /**
         * 获取订阅者名称
         * @return 订阅者名称
         */
        String getName();
    }
  3. XW订阅者实现 (NewsSubscriber.java)

    java 复制代码
    package com.example.observer;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * 具体的XW订阅者实现
     */
    public class NewsSubscriber implements Subscriber {
        private final String name;
        private final List<NewsType> subscribedTypes;
        private final List<String> receivedNews;
        
        public NewsSubscriber(String name) {
            this.name = name;
            this.subscribedTypes = new ArrayList<>();
            this.receivedNews = new ArrayList<>();
        }
        
        /**
         * 订阅特定类型的新闻
         * @param newsType XW类型
         */
        public void subscribeTo(NewsType newsType) {
            if (!subscribedTypes.contains(newsType)) {
                subscribedTypes.add(newsType);
                System.out.println(name + " 订阅了 " + newsType.getDescription());
            }
        }
        
        /**
         * 取消订阅特定类型的XW
         * @param newsType XW类型
         */
        public void unsubscribeFrom(NewsType newsType) {
            if (subscribedTypes.contains(newsType)) {
                subscribedTypes.remove(newsType);
                System.out.println(name + " 取消订阅 " + newsType.getDescription());
            }
        }
        
        /**
         * 检查是否订阅了某种类型的XW
         * @param newsType XW类型
         * @return 如果已订阅返回true,否则返回false
         */
        public boolean isSubscribedTo(NewsType newsType) {
            return subscribedTypes.contains(newsType);
        }
        
        @Override
        public void update(NewsType newsType, String newsContent) {
            // 只有订阅了该类型XW的订阅者才会收到通知
            if (subscribedTypes.contains(newsType)) {
                String newsMessage = String.format("[%s] %s: %s", 
                    newsType.getDescription(), 
                    name, 
                    newsContent);
                receivedNews.add(newsMessage);
                System.out.println(newsMessage);
            }
        }
        
        @Override
        public String getName() {
            return name;
        }
        
        /**
         * 获取所有收到的XW
         * @return 收到的XW列表
         */
        public List<String> getReceivedNews() {
            return new ArrayList<>(receivedNews);
        }
        
        /**
         * 显示所有收到的XW
         */
        public void displayReceivedNews() {
            System.out.println("\n=== " + name + " 收到的XW ===");
            if (receivedNews.isEmpty()) {
                System.out.println("暂无XW");
            } else {
                for (int i = 0; i < receivedNews.size(); i++) {
                    System.out.println((i + 1) + ". " + receivedNews.get(i));
                }
            }
            System.out.println();
        }
    }
  4. XW发布者 (NewsPublisher.java)

    java 复制代码
    package com.example.observer;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    /**
     * XW发布者(主题/被观察者)
     * 负责管理订阅者和发布XW
     */
    public class NewsPublisher {
        private final Map<NewsType, List<Subscriber>> subscribersByType;
        private final String publisherName;
        
        public NewsPublisher(String publisherName) {
            this.publisherName = publisherName;
            this.subscribersByType = new HashMap<>();
            
            // 初始化所有XW类型的订阅者列表
            for (NewsType type : NewsType.values()) {
                subscribersByType.put(type, new ArrayList<>());
            }
        }
        
        /**
         * 订阅XW
         * @param subscriber 订阅者
         * @param newsType XW类型
         */
        public void subscribe(Subscriber subscriber, NewsType newsType) {
            List<Subscriber> subscribers = subscribersByType.get(newsType);
            if (!subscribers.contains(subscriber)) {
                subscribers.add(subscriber);
                System.out.println(subscriber.getName() + " 成功订阅 " + 
                    publisherName + " 的 " + newsType.getDescription());
            }
        }
        
        /**
         * 取消订阅
         * @param subscriber 订阅者
         * @param newsType XW类型
         */
        public void unsubscribe(Subscriber subscriber, NewsType newsType) {
            List<Subscriber> subscribers = subscribersByType.get(newsType);
            if (subscribers.contains(subscriber)) {
                subscribers.remove(subscriber);
                System.out.println(subscriber.getName() + " 已取消订阅 " + 
                    publisherName + " 的 " + newsType.getDescription());
            }
        }
        
        /**
         * 发布XW
         * @param newsType XW类型
         * @param newsContent XW内容
         */
        public void publishNews(NewsType newsType, String newsContent) {
            System.out.println("\n=== " + publisherName + " 发布XW ===");
            System.out.println("【" + newsType.getDescription() + "】" + newsContent);
            System.out.println("通知订阅者...");
            
            List<Subscriber> subscribers = subscribersByType.get(newsType);
            if (subscribers.isEmpty()) {
                System.out.println("当前没有订阅者订阅此类XW");
            } else {
                // 通知所有订阅了该类型XW的订阅者
                for (Subscriber subscriber : subscribers) {
                    subscriber.update(newsType, newsContent);
                }
                System.out.println("已通知 " + subscribers.size() + " 位订阅者");
            }
        }
        
        /**
         * 获取特定XW类型的订阅者数量
         * @param newsType XW类型
         * @return 订阅者数量
         */
        public int getSubscriberCount(NewsType newsType) {
            return subscribersByType.get(newsType).size();
        }
        
        /**
         * 显示所有订阅者统计
         */
        public void displaySubscriptionStats() {
            System.out.println("\n=== " + publisherName + " 订阅统计 ===");
            for (NewsType type : NewsType.values()) {
                int count = getSubscriberCount(type);
                System.out.println(type.getDescription() + ": " + count + " 位订阅者");
            }
        }
    }
  5. 主程序 (Main.java)

    java 复制代码
    package com.example;
    
    import com.example.observer.NewsPublisher;
    import com.example.observer.NewsSubscriber;
    import com.example.observer.NewsType;
    import com.example.observer.Subscriber;
    
    /**
     * 观察者模式演示主程序
     */
    public class Main {
        public static void main(String[] args) {
            System.out.println("=== 观察者模式演示:XW发布系统 ===\n");
            
            // 创建XW发布者
            NewsPublisher newsAgency = new NewsPublisher("HQ新闻社");
            
            // 创建XW订阅者
            Subscriber alice = new NewsSubscriber("Alice");
            Subscriber bob = new NewsSubscriber("Bob");
            Subscriber charlie = new NewsSubscriber("Charlie");
            Subscriber diana = new NewsSubscriber("Diana");
            
            // 订阅者选择感兴趣的XW类型
            ((NewsSubscriber) alice).subscribeTo(NewsType.POLITICS);
            ((NewsSubscriber) alice).subscribeTo(NewsType.TECHNOLOGY);
            
            ((NewsSubscriber) bob).subscribeTo(NewsType.SPORTS);
            ((NewsSubscriber) bob).subscribeTo(NewsType.ENTERTAINMENT);
            
            ((NewsSubscriber) charlie).subscribeTo(NewsType.TECHNOLOGY);
            ((NewsSubscriber) charlie).subscribeTo(NewsType.BUSINESS);
            
            ((NewsSubscriber) diana).subscribeTo(NewsType.POLITICS);
            ((NewsSubscriber) diana).subscribeTo(NewsType.ENTERTAINMENT);
            ((NewsSubscriber) diana).subscribeTo(NewsType.BUSINESS);
            
            System.out.println("\n=== 订阅关系建立 ===\n");
            
            // 订阅者向发布者注册
            newsAgency.subscribe(alice, NewsType.POLITICS);
            newsAgency.subscribe(alice, NewsType.TECHNOLOGY);
            
            newsAgency.subscribe(bob, NewsType.SPORTS);
            newsAgency.subscribe(bob, NewsType.ENTERTAINMENT);
            
            newsAgency.subscribe(charlie, NewsType.TECHNOLOGY);
            newsAgency.subscribe(charlie, NewsType.BUSINESS);
            
            newsAgency.subscribe(diana, NewsType.POLITICS);
            newsAgency.subscribe(diana, NewsType.ENTERTAINMENT);
            newsAgency.subscribe(diana, NewsType.BUSINESS);
            
            // 显示订阅统计
            newsAgency.displaySubscriptionStats();
            
            System.out.println("\n=== 开始发布XW ===\n");
            
            // 发布新闻
            newsAgency.publishNews(NewsType.SPORTS, "中国女排获得世界冠军");
            newsAgency.publishNews(NewsType.TECHNOLOGY, "人工智能取得重大突破");
            newsAgency.publishNews(NewsType.ENTERTAINMENT, "奥斯卡颁奖典礼即将举行");
            newsAgency.publishNews(NewsType.BUSINESS, "股市今日大幅上涨");
            
            // 显示每个订阅者收到的XW
            ((NewsSubscriber) alice).displayReceivedNews();
            ((NewsSubscriber) bob).displayReceivedNews();
            ((NewsSubscriber) charlie).displayReceivedNews();
            ((NewsSubscriber) diana).displayReceivedNews();
            
            // 演示取消订阅
            System.out.println("\n=== 演示取消订阅 ===\n");
            newsAgency.unsubscribe(alice, NewsType.POLITICS);
            ((NewsSubscriber) alice).unsubscribeFrom(NewsType.POLITICS);
            
            newsAgency.displaySubscriptionStats();
            
            // 再次发布XW,Alice将不会收到ZZ新闻
            System.out.println("\n=== 再次发布ZZ新闻 ===\n");
            newsAgency.publishNews(NewsType.POLITICS, "新外交政策发布");
            
            // 显示Alice收到的ZW
            ((NewsSubscriber) alice).displayReceivedNews();
            
            // 演示使用Java内置的Observer接口(可选)
            System.out.println("\n=== 使用Java内置Observer接口示例 ===");
            demonstrateJavaBuiltInObserver();
        }
        
        /**
         * 演示使用Java内置的Observer/Observable类
         * 注意:从Java 9开始,Observable类被标记为过时(deprecated)
         * 这里仅作为示例展示
         */
        private static void demonstrateJavaBuiltInObserver() {
            System.out.println("注意:Java内置的java.util.Observable和Observer");
            System.out.println("从Java 9开始已被标记为过时(deprecated)。");
            System.out.println("推荐使用PropertyChangeListener或自定义观察者模式实现。");
        }
    }

3. 构建和运行

  1. 编译项目

    bash 复制代码
    # 编译项目
    mvn compile
  2. 打包项目

    bash 复制代码
    mvn package
  3. 运行项目

    bash 复制代码
    mvn exec:java -Dexec.mainClass="com.example.Main"
  4. 直接运行

    bash 复制代码
    # 编译
    javac -d target/classes src/main/java/com/example/observer/*.java src/main/java/com/example/Main.java
    # 运行
    java -cp target/classes com.example.Main

4. 核心概念

  • 松耦合:发布者和订阅者之间是松耦合的,可以独立修改
  • 一对多关系:一个发布者可以对应多个订阅者
  • 自动通知:当主题状态改变时,所有观察者会自动收到通知
  • 动态订阅:订阅者可以随时订阅或取消订阅

额外扩展

  • 添加过滤器:允许订阅者设置过滤器,只接收符合特定条件的新闻
  • 优先级系统:为订阅者设置优先级,高优先级订阅者先收到通知
  • 异步通知:使用线程池异步通知订阅者,提高系统响应速度
  • 持久化订阅:将订阅关系保存到数据库,重启后可以恢复
  • 推拉模式:实现推模式(主动推送)和拉模式(订阅者主动拉取)两种方式
相关推荐
apolloyhl19 小时前
观察者模式
观察者模式
一路往蓝-Anbo3 天前
STM32单线串口通讯实战(五):RTOS架构 —— 线程安全与零拷贝设计
c语言·开发语言·stm32·单片机·嵌入式硬件·观察者模式·链表
killer_queen48043 天前
设计模式-观察者模式
观察者模式·设计模式
崎岖Qiu4 天前
【设计模式笔记26】:深入浅出「观察者模式」
java·笔记·观察者模式·设计模式
一路往蓝-Anbo5 天前
C语言从句柄到对象 (八) —— 当对象会说话:观察者模式与事件链表
c语言·开发语言·数据结构·stm32·单片机·观察者模式·链表
“抚琴”的人9 天前
C#上位机观察者模式
开发语言·观察者模式·c#·上位机
小笔学长10 天前
观察者模式:实现对象间的消息传递
javascript·观察者模式·项目实战·前端开发
有一个好名字10 天前
设计模式-观察者模式
观察者模式·设计模式
WarPigs10 天前
观察者模式与事件中心
观察者模式