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

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

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

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




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

 

二、示例代码

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

角色:

1)商品:被观察者;

2)用户:观察者

 

1.商品(发布者)

 

Java代码   收藏代码
  1. import java.util.ArrayList;    
  2. import java.util.Iterator;    
  3. import java.util.List;    
  4.     
  5. /**  
  6.  * 商品-发布者  
  7.  * @author Administrator  
  8.  *  
  9.  */    
  10. public class Product {    
  11.     private String name;    
  12.     private double price;    
  13.     private List<Observer> focusUsers;//观察者集合    
  14.         
  15.     /**  
  16.      * 价格折扣  
  17.      * @param off  
  18.      */    
  19.     public synchronized void payOff(double off){    
  20.         this.price = getPrice() * (1 - off);            
  21.         StringBuffer msg = null;    
  22.             
  23.         if(focusUsers != null && !focusUsers.isEmpty()){    
  24.             Iterator it = focusUsers.iterator();    
  25.             while(it.hasNext()){    
  26.                 Observer user = (Observer)it.next();    
  27.                     
  28.                 String msgPart = ", "this.getName() +"的价格 "this.getPrice() +", 价格折扣 "+ off * 100 +"%!";    
  29.                 msg = new StringBuffer();    
  30.                 msg.append("~~~~ 您好 "+ user.getName());    
  31.                 msg.append(msgPart);    
  32.                     
  33.                 user.notify(msg.toString());//发送提醒    
  34.             }    
  35.         }    
  36.     }    
  37.         
  38.     /**  
  39.      * 添加关注用户  
  40.      * @param user  
  41.      */    
  42.     public void addFocusUsers(User user) {    
  43.         this.getFocusUsers().add(user);    
  44.     }    
  45.     /**  
  46.      * 删除关注用户  
  47.      * @param user  
  48.      */    
  49.     public void delFocusUser(User user) {    
  50.         this.getFocusUsers().remove(user);    
  51.     }    
  52.         
  53.         
  54.     public Product(){    
  55.         focusUsers = new ArrayList<Observer>();    
  56.     }    
  57.         
  58.     public String getName() {    
  59.         return name;    
  60.     }    
  61.     public void setName(String name) {    
  62.         this.name = name;    
  63.     }    
  64.     public double getPrice() {    
  65.         return price;    
  66.     }    
  67.     public void setPrice(double price) {    
  68.         this.price = price;    
  69.     }    
  70.         
  71.         
  72.     public List<Observer> getFocusUsers() {    
  73.         return focusUsers;    
  74.     }    
  75.     public void setFocusUsers(List<Observer> focusUsers) {    
  76.         this.focusUsers = focusUsers;    
  77.     }    
  78.         
  79. }    

 

 

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

 

Java代码   收藏代码
  1. /**  
  2.  * 观察者(订阅者)接口  
  3.  * @author Administrator  
  4.  *  
  5.  */    
  6. public interface Observer {    
  7.         
  8.     public void notify(String msg);    
  9.         
  10.     public String getName();    
  11.         
  12. }    

 

 

3.观察者(订阅者)

 

Java代码   收藏代码
  1. import java.util.HashSet;    
  2. import java.util.Set;    
  3.     
  4. /**  
  5.  * 观察者(订阅者)  
  6.  * @author Administrator  
  7.  *  
  8.  */    
  9. public class User implements Observer {    
  10.     private String name;    
  11.     private Set<Product> focusPdts;    
  12.         
  13.     /**  
  14.      * 通知方法  
  15.      */    
  16.     public void notify(String msg){    
  17.         System.out.println(msg);    
  18.     }    
  19.         
  20.     public User(){    
  21.         focusPdts = new HashSet<Product>();    
  22.     }    
  23.         
  24.     public String getName() {    
  25.         return name;    
  26.     }    
  27.     public void setName(String name) {    
  28.         this.name = name;    
  29.     }    
  30.     public Set<Product> getFocusPdts() {    
  31.         return focusPdts;    
  32.     }    
  33.     public void setFocusPdts(Set<Product> focusPdts) {    
  34.         this.focusPdts = focusPdts;    
  35.     }       
  36.         
  37. }   

 

 

4.client端调用

 

Java代码   收藏代码
  1. public class client {    
  2.     
  3.     /**  
  4.      * @param args  
  5.      */    
  6.     public static void main(String[] args) {    
  7.         //产品    
  8.         Product mobile = new Product();    
  9.         mobile.setName("SAMSUNG手机");    
  10.         mobile.setPrice(2000);    
  11.             
  12.         Product book = new Product();    
  13.         book.setName("JAVA设计模式");    
  14.         book.setPrice(80);    
  15.             
  16.         //用户    
  17.         User user1 = new User();    
  18.         user1.setName("张三");    
  19.         user1.getFocusPdts().add(mobile);//关注某一款三星手机    
  20.         //user1.getFocusPdts().add(book);//关注JAVA设计模式    
  21.             
  22.         User user2 = new User();    
  23.         user2.setName("李四");    
  24.         user2.getFocusPdts().add(mobile);//关注某一款三星手机    
  25.         user2.getFocusPdts().add(book);//关注JAVA设计模式    
  26.             
  27.         //建立商品和订阅者关联    
  28.         mobile.getFocusUsers().add(user1);    
  29.         book.getFocusUsers().add(user1);    
  30.         book.getFocusUsers().add(user2);    
  31.             
  32.         //产品打折,发送站内信提醒    
  33.         mobile.payOff(0.1);    
  34.         book.payOff(0.2);    
  35.     }    
  36.     
  37. }    

 

 

三、功能设计

常用的处理方式:

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

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份到数据库应对内存失效问题,采用异步持久化方式可减小对系统整体资源的消耗。

猜你喜欢

转载自fengbin2005.iteye.com/blog/2329749