观察者模式(发布-订阅)

由于网站带有弱sns功能,因此需要设计关注和被关注的消息或是动作通知,那么将这个需求抽象出来的时候就会发现正好符合java中发布订阅模式。

一、概述
Java 的设计模式很多,观察者模式被称为是模式中的皇后,而且Java jdk也对它做了实现,可见该设计模式的重要位置。在图形化设计的软件中,为了实现视图和事件处理的分离,大多都采用了Observer模式,比如 Java的Swing,Flex的ActionScript等。在现实的应用系统中也有好多应用,比如像当当网、京东商城一类的电子商务网站,如果你对某 件商品比较关注,可以放到收藏架,那么当该商品降价时,系统给您发送站内消息、手机短信、邮件。这就是观察者模式的一个典型应用,商品是被观察者,关注该商品的客户 就是观察者。

GoF说道:Observer模式的意图是“定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新”。参见下图:




可以看出来,观察者模式,是一种一对多的关系,即多个观察者监听一个主题。

 

二、示例代码

商品价格打折后,所有关注、收藏该商品的用户都收到相关的信息提醒。

角色:

1)商品:被观察者;

2)用户:观察者

 

1.商品(发布者)

 

    import java.util.ArrayList;  
    import java.util.Iterator;  
    import java.util.List;  
      
    /** 
     * 商品-发布者 
     * @author Administrator 
     * 
     */  
    public class Product {  
        private String name;  
        private double price;  
        private List<Observer> focusUsers;//观察者集合  
          
        /** 
         * 价格折扣 
         * @param off 
         */  
        public synchronized void payOff(double off){  
            this.price = getPrice() * (1 - off);          
            StringBuffer msg = null;  
              
            if(focusUsers != null && !focusUsers.isEmpty()){  
                Iterator it = focusUsers.iterator();  
                while(it.hasNext()){  
                    Observer user = (Observer)it.next();  
                      
                    String msgPart = ", "+ this.getName() +"的价格 "+ this.getPrice() +", 价格折扣 "+ off * 100 +"%!";  
                    msg = new StringBuffer();  
                    msg.append("~~~~ 您好 "+ user.getName());  
                    msg.append(msgPart);  
                      
                    user.notify(msg.toString());//发送提醒  
                }  
            }  
        }  
          
        /** 
         * 添加关注用户 
         * @param user 
         */  
        public void addFocusUsers(User user) {  
            this.getFocusUsers().add(user);  
        }  
        /** 
         * 删除关注用户 
         * @param user 
         */  
        public void delFocusUser(User user) {  
            this.getFocusUsers().remove(user);  
        }  
          
          
        public Product(){  
            focusUsers = new ArrayList<Observer>();  
        }  
          
        public String getName() {  
            return name;  
        }  
        public void setName(String name) {  
            this.name = name;  
        }  
        public double getPrice() {  
            return price;  
        }  
        public void setPrice(double price) {  
            this.price = price;  
        }  
          
          
        public List<Observer> getFocusUsers() {  
            return focusUsers;  
        }  
        public void setFocusUsers(List<Observer> focusUsers) {  
            this.focusUsers = focusUsers;  
        }  
          
    }  

 

 

2.观察者(订阅者)接口

 

    /** 
     * 观察者(订阅者)接口 
     * @author Administrator 
     * 
     */  
    public interface Observer {  
          
        public void notify(String msg);  
          
        public String getName();  
          
    }  

 

 

3.观察者(订阅者)

 

import java.util.HashSet;  
import java.util.Set;  
  
/** 
 * 观察者(订阅者) 
 * @author Administrator 
 * 
 */  
public class User implements Observer {  
    private String name;  
    private Set<Product> focusPdts;  
      
    /** 
     * 通知方法 
     */  
    public void notify(String msg){  
        System.out.println(msg);  
    }  
      
    public User(){  
        focusPdts = new HashSet<Product>();  
    }  
      
    public String getName() {  
        return name;  
    }  
    public void setName(String name) {  
        this.name = name;  
    }  
    public Set<Product> getFocusPdts() {  
        return focusPdts;  
    }  
    public void setFocusPdts(Set<Product> focusPdts) {  
        this.focusPdts = focusPdts;  
    }     
      
} 

 

 

4.client端调用

 

    public class client {  
      
        /** 
         * @param args 
         */  
        public static void main(String[] args) {  
            //产品  
            Product mobile = new Product();  
            mobile.setName("SAMSUNG手机");  
            mobile.setPrice(2000);  
              
            Product book = new Product();  
            book.setName("JAVA设计模式");  
            book.setPrice(80);  
              
            //用户  
            User user1 = new User();  
            user1.setName("张三");  
            user1.getFocusPdts().add(mobile);//关注某一款三星手机  
            //user1.getFocusPdts().add(book);//关注JAVA设计模式  
              
            User user2 = new User();  
            user2.setName("李四");  
            user2.getFocusPdts().add(mobile);//关注某一款三星手机  
            user2.getFocusPdts().add(book);//关注JAVA设计模式  
              
            //建立商品和订阅者关联  
            mobile.getFocusUsers().add(user1);  
            book.getFocusUsers().add(user1);  
            book.getFocusUsers().add(user2);  
              
            //产品打折,发送站内信提醒  
            mobile.payOff(0.1);  
            book.payOff(0.2);  
        }  
      
    }  

 

 

三、功能设计

常用的处理方式:

将数据库作为数据存储的介质,消息提醒数据保存在数据库表中,采用定时任务的方式来汇总和发送。具体流程:

1.存储用户-关注关联数据

将用户和所关注的数据存储到一张“用户-关注商品关联表”;

 

2.执行汇总任务

商品打折时,触发汇总任务,遍历“用户-关注商品“关联表,将符合发送条件的记录汇总到”提醒消息表“;数据量巨大的情况下,可采用在“用户-关注商品关联表”冗余字段的方式,不再创建”提醒消息表“减小数据量。

 

3.发送折扣提醒消息

遍历”提醒消息表“并发送,发送完成后,将记录标示为已发送。

 

四、设计分析

如果系统的用户、商品数量都很大,这种情况下如何设计功能更合理呢,个人认为有几点需要关注:

1)响应及时性

2)数据的持久性

3)web层压力

4)数据库层压力

5)系统资源的消耗

 

内存方式:   采用观察者模式,将关注用户保存在商品对象中,也就是存储在java 堆中。

数据库方式:采用传统关系型数据,例如mysql等。

 


项目

内存

数据库

分析

响应及时性

较好

较差

内存操作比起数据库操作肯定性能上好很多

数据的持久性

较差

较好

如果出现宕机等故障,内存数据会被清空,导致整个功能异常。

所以说数据仅保存在内存有缺陷,将内存数据持久化到数据库中做备份是较完备的方案,具体实现暂不讨论。

web层压力

较大

中等

由于用户和商品数量巨大,商品-内存关联数据保存在内存中对系统内存消耗较大,假如1000W用户,每个用户关注10个商品的话,每条记录100Byte,那么大致占用10G左右,直觉上对内存占用较大,会影响整个系统的表现。

数据库层压力

较大

用户-商品关注关联表,约1亿条数据。发送提醒消息和更改发送标示需要1次读操作、1次写操作,对数据的存储和数据库的压力都是一个挑战。另外,实现上肯定要采用缩小每次读写操作的数据集的方式。

系统资源消耗

中等

中等

内存方式,对系统内存占用较大,但对其他系统资源消耗不大。数据库方式,对系统的数据库层有较大的压力。

 

通过以上分析,发现两种方式都有比较大的问题,那是否可以采用key-value型内存软件加持久数据到数据库中的方式来实现呢?
1)key-value型内存操作比直接数据库操作(磁盘io)操作性能上好很多;
2)将内存数据保存1份到数据库应对内存失效问题,采用异步持久化方式可减小对系统整体资源的消耗。
 

 

<think>我们正在讨论观察者模式发布-订阅模式的区别。根据提供的引用,我们可以总结如下: 引用[1]指出,发布-订阅模式观察者模式多了一个调度中心(或事件通道),这使得发布者和订阅者之间没有直接联系,而是通过调度中心来管理。这种设计提高了灵活性。 引用[2]进一步说明,发布-订阅模式是基于一个事件通道的,订阅者通过自定义事件订阅主题,发布者则通过发布主题事件来通知订阅者。 引用[3]则强调观察者模式中,被观察对象(Subject)和观察者(Observer)是直接关联的,Subject需要维护一个观察者列表,并提供添加、删除和通知观察者的方法。 因此,我们可以将两者的区别归纳如下: 1. **耦合度**: - 观察者模式:Subject和Observer直接交互,Subject知道Observer的存在(通过维护观察者列表)。 - 发布-订阅模式发布者和订阅者不直接交互,而是通过一个中间件(事件通道或调度中心)进行通信,实现了完全解耦。 2. **通信方式**: - 观察者模式:Subject在状态变化时,直接调用Observer的更新方法(如`update()`)。 - 发布-订阅模式发布者将事件发布到调度中心,调度中心根据事件类型将事件分发给相应的订阅者。 3. **灵活性**: - 发布-订阅模式由于中间件的存在,可以支持更复杂的场景,例如过滤事件、多对多通信、异步处理等。而观察者模式通常是同步的,并且是一对多的关系。 4. **实现复杂度**: - 观察者模式实现较为简单,通常只需要在Subject中维护一个观察者列表。 - 发布-订阅模式需要实现一个调度中心,相对复杂。 下面用代码示例说明: **观察者模式示例**: ```dart // 被观察者(Subject) class Subject { List<Observer> _observers = []; void addObserver(Observer observer) { _observers.add(observer); } void removeObserver(Observer observer) { _observers.remove(observer); } void notifyObservers() { for (var observer in _observers) { observer.update(); } } } // 观察者(Observer) abstract class Observer { void update(); } // 具体观察者 class ConcreteObserver implements Observer { @override void update() { print('Observer notified!'); } } ``` **发布-订阅模式示例**: ```dart // 事件调度中心 class EventBus { final Map<String, List<Function>> _subscriptions = {}; void subscribe(String eventType, Function callback) { _subscriptions.putIfAbsent(eventType, () => []).add(callback); } void publish(String eventType) { if (_subscriptions.containsKey(eventType)) { for (var callback in _subscriptions[eventType]!) { callback(); } } } } // 发布者 class Publisher { final EventBus eventBus; Publisher(this.eventBus); void publishEvent(String eventType) { eventBus.publish(eventType); } } // 订阅者 class Subscriber { final EventBus eventBus; Subscriber(this.eventBus); void subscribe(String eventType, Function callback) { eventBus.subscribe(eventType, callback); } } ``` 在发布-订阅模式中,发布者和订阅者通过事件总线(EventBus)进行通信,双方互不认识。 总结: | 特性 | 观察者模式 | 发布-订阅模式 | |--------------|------------------------------|----------------------------------| | **耦合度** | 紧耦合(Subject直接引用Observer) | 松耦合(通过中间件) | | **通信方式** | 直接调用 | 通过事件通道 | | **关系** | 通常是一对多 | 多对多(通过不同事件类型) | | **灵活性** | 较低 | 高(支持事件过滤、异步等) | | **复杂度** | 简单 | 相对复杂 | 在Flutter中,观察者模式(如`ChangeNotifier`)是直接通知监听器的,而发布-订阅模式可以使用事件总线(如`event_bus`库)或`Stream`来实现。例如,`Stream`和`StreamController`就构成了发布-订阅模式,其中`StreamController`作为发布者,`StreamSubscription`作为订阅者,而`Stream`本身则充当了事件通道的角色[^1][^2]。 §§相关问题§§ 1. 在Flutter中如何实现一个事件总线(Event Bus)? 2. 观察者模式发布-订阅模式各自的优缺点是什么? 3. 如何选择使用观察者模式还是发布-订阅模式?</think>### 观察者模式 vs 发布-订阅模式的区别 #### 1. **核心架构差异** | 特性 | 观察者模式 | 发布-订阅模式 | |--------------|------------------------------|----------------------------------| | **中间层** | 无直接中介 | 存在事件通道/调度中心[^1] | | **耦合度** | 观察者与被观察者直接绑定 | 发布者与订阅者完全解耦[^2] | | **通信方式** | 被观察者主动通知观察者 | 通过调度中心路由事件[^3] | #### 2. **实现机制对比 **观察者模式**(直接通知): ```dart // 被观察者 class Subject { final List<Observer> _observers = []; void addObserver(Observer o) => _observers.add(o); void notify() => _observers.forEach((o) => o.update()); } // 观察者 abstract class Observer { void update(); } ``` **发布-订阅模式**(通过事件总线): ```dart // 事件总线(调度中心) class EventBus { final Map<String, List<Function>> _subscriptions = {}; void subscribe(String event, Function callback) { _subscriptions.putIfAbsent(event, () => []).add(callback); } void publish(String event) { _subscriptions[event]?.forEach((fn) => fn()); } } // 使用示例 final bus = EventBus(); bus.subscribe('user_login', () => print("Login handled")); bus.publish('user_login'); // 通过调度中心触发 ``` #### 3. **关键区别点** 1. **关系建立方式** - 观察者模式:被观察者**主动管理**观察者列表(添加/删除)[^3] - 发布-订阅订阅者**向调度中心注册**,发布者**不知订阅者存在**[^1] 2. **事件处理能力** - 观察者模式:通常处理**单一主题**变更 - 发布-订阅支持**多主题事件过滤**(如`eventBus.subscribe('topic.*')`)[^2] 3. **系统复杂度** - 观察者模式:适合**简单依赖关系**(如按钮点击监听) - 发布-订阅:解决**跨组件/跨系统通信**(如微服务事件驱动)[^1] #### 4. **应用场景差异** - **观察者模式适用**: ✅ 局部状态更新(如Flutter的`ValueNotifier`) ✅ 父子组件直接通信 ✅ 简单的一对多通知场景 - **发布-订阅适用**: ✅ 微服务间异步通信(如Kafka) ✅ 插件系统事件调度 ✅ 需要事件过滤的复杂系统(`firebase_messaging`的topic订阅) > 两者本质都是**事件驱动范式**,但发布-订阅通过调度中心实现更彻底的解耦,为系统提供更强的扩展性和灵活性[^1][^2]。在Flutter中,`Stream`+`StreamController`可视为轻量级发布-订阅实现。 --- ### 数学关系表达 两者的事件传递可用函数描述: - **观察者模式**:直接映射 $$f: Subject \times Observer \rightarrow Notification$$ - **发布-订阅**:通过调度中心$S$中转 $$f: Publisher \times Event \xrightarrow{S} Subscriber$$ ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值