观察者模式--发布/订阅模式

观察者模式一定要学习,估计我得花几天的时间,今天先把题目写好,待我学成归来在总结。


  描述:
  
  在设计一组依赖的对象与它们所依赖的对象之间一致(同步)的交流模型时,观察者模式(Observer Pattern)很有用。它可以使依赖对象的状态与它们所依赖的对象的状态保持同步。这组依赖的对象指的是观察者(Observer),它们所依赖的对象称为主题(Subject)。为了实现观察者(Observer)的状态与主题(Subject)保持同步,观察者模式(Observer Pattern)
  
  推荐采用发布者--订阅者(publisher--subscriber)模型,以使这组观察者(Observer)和主题(Subject)对象之间有清晰的界限。
  
  典型的观察者(Observer)是一个依赖于或者关注于主题对象的状态的对象。一个主题可以有一个或者多个观察者。这些观察者在主体的状态发生变化时,需要得到通知。
  
  由于给定主体的观察者链表需要动态的变化,因此一个主题不能维护一个静态的观察者链表。因此关注于主题状态的任何对象都需要明确地注册自己为主体的一个观察者。主题状态发生的变化,都需要通知所有的以注册的观察者。从主题接到通知以后,每一个观察者查询主题,使自己的状态与主题的同步。因此一个主题扮演着发布者的角色,发布信息到所有的以订阅的观察者。
  
  换句话说,主题和它的观察者之间包含了一对多的关系。当主题的实例的状态发生变化时,所有的依赖于它的观察者都会得到通知并更新自己。每一个观察者对象需要向主题注册,当主题的状态发生变化的时候得到通知。一个观察者可以注册或者订阅多个主题。当观察者不希望再得到通知时,它可以向主题进行注销。

为了实现这种机制:
  
  (1)  主题需要为注册和注销通知提供一个接口。
  
  (2)  下面的两点也需要满足:
  
  A、  拉模型(In the pull model)--主题需要提供一个接口,可以使观察者查询主题获得需要的状态信息来更新自己的状态。
  
  B、  推模型(In the push model)--主题发送观察者可能关注的状态信息。
  
  (3)  观察者需要提供一个可以从主题接受通知的接口。
  
  类图(图1)描述了为满足于以上需求,不同类的结构和它们之间的关联关系。
  

 
  Figure 1: Generic Class Association When the Observer Pattern Is Applied
  

  从这个类图可以看到:
  
  (1)  所有的主题需要提供一个类似于Observable接口的实现。
  
  (2)  所有的观察者需要提供一个类似于Observer接口的实现。
  
  在应用观察者模式时,有几种变体。这就会产生不同类型的主题--观察者模式,例如,观察者仅关注主体特定类型的变化等。
  
  增加新的观察者:
  
  应用观察者模式以后,在不影响主题类的情况下,可以动态的加入不同的观察者。同样,主题的状态变化逻辑改变时,观察者也不会受到影响。

二、示例代码

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

角色:

1)商品:被观察者;

2)用户:观察者

 

1.商品(发布者)

package com.csdnproject.observer.model1;

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 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();
	}

	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 getFocusUsers() {
		return focusUsers;
	}

	public void setFocusUsers(List focusUsers) {
		this.focusUsers = focusUsers;
	}

}

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

package com.csdnproject.observer.model1;

/** 
 * 观察者(订阅者)接口 
 * @author Administrator 
 * 
 */
public interface Observer {

	public void notify(String msg);

	public String getName();

}

3.观察者(订阅者)

package com.csdnproject.observer.model1;

import java.util.HashSet;
import java.util.Set;

/** 
 * 观察者(订阅者) 
 * @author Administrator 
 * 
 */
public class User implements Observer {
	private String name;
	private Set focusPdts;

	/** 
	 * 通知方法 
	 */
	public void notify(String msg) {
		System.out.println(msg);
	}

	public User() {
		focusPdts = new HashSet();
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Set getFocusPdts() {
		return focusPdts;
	}

	public void setFocusPdts(Set focusPdts) {
		this.focusPdts = focusPdts;
	}

}

4.client端调用

package com.csdnproject.observer.model1;

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

你可能感兴趣的:(散文,设计模式,java,发布,观察者,消息)