【JAVA设计模式-第四课】观察者模式-屌丝求职记+新闻订阅
- 观察者模式
观察者模式又称依赖模式或发布-订阅模式。
定义对象间的一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖它的对象都得到通知并被自动更新。
原文:
Observer Pattern(Another Name:Dependents , Publish-Subscribe)
Define a one-to-many dependency between objects so that when one object changes state , all its dependents are notified and update automaically .
--《Design Patterns》GOF
在实际编程中,我们经常会遇到多个对象请求一个对象,或者说多个对象需要同一个对象的数据变化,而且这多个对象都希望跟踪这个特殊对象中的数据变化。举个简单的例子:新浪微博中的关注。小弟比较喜欢一些冷笑话、内涵图什么的,所以就关注了@冷笑话、@内涵图什么的。当然除了我之外,还有其他屌丝男屌丝女喜欢冷笑话。所以当冷笑话发表一条微博的时候,所有关注它的用户都将收到这条微博信息。这是一对多的关系,而且有点数据“推送”(观察者模式中确实存在数据的“推/拉”,下文也会讲解关于观察者模式中的数据“推/拉”)的味道。好了,有那么一天,我不喜欢这个冷笑话,那么我就取消了对它的关注,以后呢,也就自然而然的看不到它的微博动态变化了。
好、言归正传。
观察者模式是关于多个对象想知道一个对象中数据变化情况的一种成熟模式。在观察者模式中有一个称作“主题”的对象和若干个称作“观察者”的对象。主题和观察者的之间的关系是一对多的关系。就像微博用户@冷笑话和关注它的众屌丝们(我也是其中一员),@冷笑话就是“主题”,而我们就是“观察者”。当然了,你马上就会想到,观察者不同样可以拥有多个主题吗?当然可以了,如果这样的话,也就成了多对多的关系了。当然我们如果需要的话,也可将多对多关系细化为双向的一对多关系(即一个主题可有多个观察者,一个观察者同样可以拥有多个主题。)。STOP!!扯远了。根据《设计模式》原文我们可以知道“观察者模式”是针对多个对象与一个对象之间数据的关系,而且强调的是这多个对象都想知道这唯一对象的数据变化。当“主题”的状态发生了变化,那么所有的“观察者”都将得到通知。
下面简单的阐述下“观察者模式”中角色及角色所担任的职责。
- 主题(Subject):主题是一个接口,该接口规定了具体主题(ConcreteSubject)需要实现的方法,如:对观察者的CRUD(增、查、改、删)方法以及通知观察者更新数据。
- 具体主题(ConcreteSubject):实现了接口主题的一个实例,包含有经常发生变化的数据。具体主题是一个集合,存放观察者的引用,以便数据变化时通知观察者。
- 观察者(Observer):观察者也是一个接口,这里规定了具体观察者更新数据的方法。
- 具体观察者(ConcreteObserver):具体观察者实现了观察者接口。在这个具体观察者中,其包含了可以存放具体主题引用的主题接口变量,以便具体观察者让具体主题将自己的引用添加到具体主题的集合中去,使自己成为它的观察者,或让这个具体主题将自己从具体主题的集合中剔除(删除/移除),使自己不再是它的观察者。
下面我们于“屌丝求职”为例子,屌丝求职者为“具体观察员”(ConcreteObServer),求职中心为“具体主题”(ConcreteSubject)。求职者要想知道求职中心的数据信息,首先要在求职中心登记自己的信息,这样求职中心才能向求职者发送(招聘)信息;当求职者不想收到求职中心的数据信息时,就可以要求求职中心(工作人员)删除自己的信息。
观察者模式的UML类图,如下:
代码实现,如下:
- 观察者
1 /** 2 * 观察者-定义一个更新数据的方法 3 * @author <a href="mailto:weijunqiang2010@gmail.com">Ajunboys</a> 4 * 5 * @since 2013-6-4 6 */ 7 public interface ObServer { 8 /** 9 * 通过查看邮箱,来获取求职中心想观察者发送的数据信息(相当于UML类图中的update()方法) 10 * @param message 11 */ 12 public abstract void seeMail(String message); 13 }
- 主题
1 /** 2 * 主题-定义了增加、删除以及向观察者更新数据的方法 3 * @author <a href="mailto:weijunqiang2010@gmail.com">Ajunboys</a> 4 * 5 * @since 2013-6-4 6 */ 7 public interface Subject { 8 /** 9 * 增加观察者 10 * @param obServer 11 */ 12 public abstract void addObserver(ObServer obServer); 13 /** 14 * 删除观察者 15 * @param obServer 16 */ 17 public abstract void deleteObserver(ObServer obServer); 18 /** 19 * 通知观察者信息更新 20 */ 21 public abstract void notifyObservers(); 22 }
- 具体主题
1 import java.util.ArrayList; 2 import java.util.List; 3 4 /** 5 * 具体主题 6 * @author <a href="mailto:weijunqiang2010@gmail.com">Ajunboys</a> 7 * 8 * @since 2013-6-4 9 */ 10 public class ConcreteSubject implements Subject{ 11 private String message; 12 //信息是否存在或最新 13 boolean changed; 14 15 //存放观察者引用的集合 16 private List<ObServer> obServers ; 17 18 public ConcreteSubject() { 19 obServers = new ArrayList<ObServer>(); 20 message = ""; 21 changed = false; 22 } 23 24 @Override 25 public void addObserver(ObServer obServer) { 26 if (!obServers.contains(obServer)) 27 obServers.add(obServer); 28 } 29 30 @Override 31 public void deleteObserver(ObServer obServer) { 32 if(obServers.contains(obServer)) 33 obServers.remove(obServer); 34 } 35 36 @Override 37 public void notifyObservers() { 38 if (changed) { 39 //通知所有观察者 40 for (ObServer obServer : obServers) { 41 //查看邮件信息 42 obServer.seeMail(message); 43 } 44 changed = false; 45 } 46 } 47 48 /** 49 * 设置最新的招聘信息 50 * @param mess 51 */ 52 public void newMessage(String mess){ 53 if (mess.equals(message)) { 54 changed = false; 55 }else { 56 message = mess; 57 changed = true; 58 } 59 } 60 }
- 具体观察者-程序员
1 /** 2 * 具体观察者-程序员 3 * @author <a href="mailto:weijunqiang2010@gmail.com">Ajunboys</a> 4 * 5 * @since 2013-6-6 6 */ 7 public class Programmer implements ObServer{ 8 private Subject subject; 9 10 /** 11 * 12 * @param subject 13 */ 14 public Programmer(Subject subject){ 15 this.subject = subject; 16 subject.addObserver(this); 17 } 18 19 @Override 20 public void seeMail(String message) { 21 System.out.println("【通知】程序员:"); 22 System.out.println(message); 23 } 24 25 }
- 具体观察者-软件工程师
1 /** 2 * 具体观察者-工程师 3 * @author <a href="mailto:weijunqiang2010@gmail.com">Ajunboys</a> 4 * 5 * @since 2013-6-6 6 */ 7 public class Engineer implements ObServer{ 8 private Subject subject; 9 10 11 /** 12 * @param subject 13 */ 14 public Engineer(Subject subject) { 15 this.subject = subject; 16 subject.addObserver(this); 17 } 18 19 20 @Override 21 public void seeMail(String message) { 22 System.out.println("【通知】软件工程师:"); 23 System.out.println(message); 24 } 25 26 }
- 程序运行类
演示一个程序员和工程师手收到求职中心发来的消息
1 /** 2 * 观察者模式-应用 3 * @author <a href="mailto:weijunqiang2010@gmail.com">Ajunboys</a> 4 * 5 * @since 2013-6-6 6 */ 7 public class Main { 8 public static void main(String[] args) { 9 JobCenter center = new JobCenter(); 10 Programmer programmer = new Programmer(center); 11 Engineer engineer = new Engineer(center); 12 center.newMessage("野狼软件开发工作室需要 5 个JAVA程序员、1个JAVA软件工程师"); 13 center.notifyObservers(); 14 center.newMessage("科腾游戏需要 2 个视觉设计师"); 15 center.notifyObservers(); 16 17 } 18 }
- 运行结果
【通知】程序员: 野狼软件开发工作室需要 5 个JAVA程序员、1个JAVA软件工程师 【通知】软件工程师: 野狼软件开发工作室需要 5 个JAVA程序员、1个JAVA软件工程师 【通知】程序员: 科腾游戏需要 2 个视觉设计师 【通知】软件工程师: 科腾游戏需要 2 个视觉设计师
之前,举了个关于微博关注的例子,就是@冷笑话什么的。其实在微博中,对数据处理是有“推”数据和“拉”数据的。有篇博文叫“新浪微博网站的技术架构详细分析 讲解到很多技术难点分解与实现方法” ,博文里也有提及新浪微博在处理大数据时的技术实现,其中就有提到“推”数据这部分。
下图:为博文部分截图。
- 观察者模式中的“推”数据
推数据方式指具体主题将变化后的数据全部交给(通知)具体观察者,即将变化后的数据传递给具体观察者用于更新方法的参数。当具体主题认为具体观察者需要这些变换后的全部数据时往往采用推数据方式。比如:@冷笑话有很多粉丝,它发表了一条新的微博,那么它就会通知所有关注它的粉丝(可能只是已登录在线的粉丝。)自己的动态。再如上面代码实现观察者模式中就是一种主题向观察者发送数据,也是“推”数据的方式。
- 观察者模式中的“拉”数据
拉数据方式是指具体主题不将变化后的数据交给(通知)具体观察者,而是提供了获得这些数据的方法,具体观察者在得到通知后,可以调用具体主题提供的方法得到数据(观察者自己把数据"拉"取出来),但需要自己判断数据是否发生了变化。当具体主题不知道具体观察者是否需要这些变换后的数据时往往采用拉数据的方式。比如:在新闻中心,客户端会主动通过一些方法(HTTP定时器拉数据、轮询等)向后台服务器获取是否有最新的(未读取)新闻动态。
个人理解:服务器主动向用户(客户端)发送数据,就是“推”数据;而用户(客户端)向服务器主动请求数据,则是“拉”数据。
下面接着模拟一下观察者模式中的“拉”数据。
下一课:【JAVA设计模式-第四课】观察者模式-屌丝求职记+新闻订阅
--------------------------------------------------------------
参考资料:《JAVA设计模式》