由于网站带有弱sns功能,因此需要设计关注和被关注的消息或是动作通知,那么将这个需求抽象出来的时候就会发现正好符合java中发布订阅模式。
一、概述
Java 的设计模式很多,观察者模式被称为是模式中的皇后,而且Java jdk也对它做了实现,可见该设计模式的重要位置。在图形化设计的软件中,为了实现视图和事件处理的分离,大多都采用了Observer模式,比如 Java的Swing,Flex的ActionScript等。在现实的应用系统中也有好多应用,比如像当当网、京东商城一类的电子商务网站,如果你对某 件商品比较关注,可以放到收藏架,那么当该商品降价时,系统给您发送站内消息、手机短信、邮件。这就是观察者模式的一个典型应用,商品是被观察者,关注该商品的客户 就是观察者。
GoF说道:Observer模式的意图是“定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新”。参见下图:
可以看出来,观察者模式,是一种一对多的关系,即多个观察者监听一个主题。
二、示例代码
商品价格打折后,所有关注、收藏该商品的用户都收到相关的信息提醒。
角色:
1)商品:被观察者;
2)用户:观察者
1.商品(发布者)
1. import
2. import
3. import
4.
5. /**
6. * 商品-发布者
7. * @author Administrator
8. *
9. */
10. public class
11. private
12. private double
13. private List<Observer> focusUsers;//观察者集合
14.
15. /**
16. * 价格折扣
17. * @param off
18. */
19. public synchronized void payOff(double
20. this.price = getPrice() * (1
21. null;
22.
23. if(focusUsers != null
24. Iterator it = focusUsers.iterator();
25. while(it.hasNext()){
26. Observer user = (Observer)it.next();
27.
28. ", "+ this.getName() +"的价格 "+ this.getPrice() +", 价格折扣 "+ off * 100 +"%!";
29. new
30. "~~~~ 您好 "+ user.getName());
31. msg.append(msgPart);
32.
33. //发送提醒
34. }
35. }
36. }
37.
38. /**
39. * 添加关注用户
40. * @param user
41. */
42. public void
43. this.getFocusUsers().add(user);
44. }
45. /**
46. * 删除关注用户
47. * @param user
48. */
49. public void
50. this.getFocusUsers().remove(user);
51. }
52.
53.
54. public
55. new
56. }
57.
58. public
59. return
60. }
61. public void
62. this.name = name;
63. }
64. public double
65. return
66. }
67. public void setPrice(double
68. this.price = price;
69. }
70.
71.
72. public
73. return
74. }
75. public void
76. this.focusUsers = focusUsers;
77. }
78.
79. }
2.观察者(订阅者)接口
1. /**
2. * 观察者(订阅者)接口
3. * @author Administrator
4. *
5. */
6. public interface
7.
8. public void
9.
10. public
11.
12. }
3.观察者(订阅者)
1. import
2.
3. /**
4. * 观察者(订阅者)
5. * @author Administrator
6. *
7. */
8. public class User implements
9. private
10. private
11.
12. /**
13. * 通知方法
14. */
15. public void
16. System.out.println(msg);
17. }
18.
19. public
20. new
21. }
22.
23. public
24. return
25. }
26. public void
27. this.name = name;
28. }
29. public
30. return
31. }
32. public void
33. this.focusPdts = focusPdts;
34. }
35.
36. }
4.client端调用
1. public class
2.
3. /**
4. * @param args
5. */
6. public static void
7. //产品
8. new
9. "SAMSUNG手机");
10. 2000);
11.
12. new
13. "JAVA设计模式");
14. 80);
15.
16. //用户
17. new
18. "张三");
19. //关注某一款三星手机
20. //user1.getFocusPdts().add(book);//关注JAVA设计模式
21.
22. new
23. "李四");
24. //关注某一款三星手机
25. //关注JAVA设计模式
26.
27. //建立商品和订阅者关联
28. mobile.getFocusUsers().add(user1);
29. book.getFocusUsers().add(user1);
30. book.getFocusUsers().add(user2);
31.
32. //产品打折,发送站内信提醒
33. 0.1);
34. 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份到数据库应对内存失效问题,采用异步持久化方式可减小对系统整体资源的消耗。