定义对象间的一种一对多的依赖关系。当一个对象的状态发生改变时,
所有依赖于它的对象都得到通知并被自动更新。
观察者模式又被称为发布——订阅模式。
@@@练习示例:
订阅报纸
@@@示例代码:
\src\pattern\Subject.java
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
package pattern; import java.util.ArrayList; import java.util.List; /** * 目标对象,作为被观察者 */ public class Subject { /** * 用来保存注册的观察者对象,也就是报纸的订阅者 */ private List<Observer> readers = new ArrayList<Observer>(); /** * 报纸的读者需要向报社订阅,先要注册 * @param reader 报纸的读者 */ public void attach(Observer reader) { readers.add(reader); } /** * 报纸的读者可以取消订阅 * @param reader 报纸的读者 */ public void detach(Observer reader) { readers.remove(reader); } /** * 当每期报纸印刷出来后,就要迅速主动地被送到读者的手中 * 相当于通知读者,让他们知道 */ protected void notifyObservers() { for (Observer reader : readers) { reader.update(this); } } }
\src\pattern\Observer.java
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
package pattern; /** * 观察者,比如报纸的读者 */ public interface Observer { /** * 被通知的方法 * @param subject 具体的目标对象,可以获取报纸的内容 */ public void update(Subject subject); }
\src\pattern\NewsPaper.java
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
package pattern; /** * 报纸对象,具体的目标对象 */ public class NewsPaper extends Subject { /** * 报纸的具体内容 */ private String content; /** * 获取报纸的具体内容 * @return 报纸的具体内容 */ public String getContent() { return content; } /** * 示意,设置报纸的具体内容,相当于要出版报纸了 * @param content 报纸的具体内容 */ public void setContent(String content) { this.content = content; // 内容有了,说明又出版报纸了,那就通知所有的读者 notifyObservers(); } }
\src\pattern\Reader.java
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
package pattern; /** * 真正的读者,为了简单就描述一下姓名 */ public class Reader implements Observer { /** * 读者的姓名 */ private String name; @Override public void update(Subject subject) { // 这里采用拉的方式 System.out.println(name + "收到报纸了,阅读它,内容是:" + ((NewsPaper)subject).getContent()); } public String getName() { return name; } public void setName(String name) { this.name = name; } }
\src\user\Client.java
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
package user; import pattern.NewsPaper; import pattern.Reader; public class Client { public static void main(String[] args) { // 创建一份报纸,作为被观察者 NewsPaper subject = new NewsPaper(); // 创建读者,也就是观察者 Reader reader1 = new Reader(); reader1.setName("张三"); Reader reader2 = new Reader(); reader2.setName("李四"); Reader reader3 = new Reader(); reader3.setName("王五"); // 注册读者 subject.attach(reader1); subject.attach(reader2); subject.attach(reader3); // 出版报纸 subject.setContent("本期的内容是观察者模式"); } }
[---使用JAVA自带的观察者模式---]
\src\pattern\NewsBook.java
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
package pattern; /** * 报纸对象,具体的目标实现 */ public class NewsBook extends java.util.Observable { /** * 报纸的具体内容 */ private String content; /** * 获取报纸的具体内容 * @return 报纸的具体内容 */ public String getContent() { return content; } /** * 示意,设置报纸的具体内容,相当于要出版报纸了 * @param content 报纸的具体内容 */ public void setContent(String content) { this.content = content; // 内容有了,说明又出版报纸了,那就通知所有的读者 // 注意在使用Java中的Observer模式的时候,下面这句话不可少 this.setChanged(); // 然后主动通知,这里用的是推的方式 this.notifyObservers(this.content); // 如果用拉的方式,这么调用 // this.notifyObservers(); } }
\src\pattern\BookReader.java
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
package pattern; import java.util.Observable; /** * 真正的读者,为了简单就描述一下姓名 */ public class BookReader implements java.util.Observer { /** * 读者的姓名 */ private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public void update(Observable o, Object obj) { // 这里采用推的方式 System.out.println(name + "收到报纸了,阅读它,目标推过来的内容是:" + obj); // 这里获取拉的数据 System.out.println(name + "收到报纸了,阅读它,主动到目标对象去拉的内容是:" + ((NewsBook)o).getContent()); } }
\src\user\BookClient.java
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
package user; import pattern.BookReader; import pattern.NewsBook; public class BookClient { public static void main(String[] args) { // 创建一份报纸,作为被观察者 NewsBook subject = new NewsBook(); // 创建读者,也就是观察者 BookReader reader1 = new BookReader(); reader1.setName("张三"); BookReader reader2 = new BookReader(); reader2.setName("李四"); BookReader reader3 = new BookReader(); reader3.setName("王五"); // 注册读者 subject.addObserver(reader1); subject.addObserver(reader2); subject.addObserver(reader3); // 出版报纸 subject.setContent("本期的内容是观察者模式"); } }
@@@模式的实现:
1) 目标和观察者之间一般是一对多,也可以一对一;
2) 观察者可以观察多个目标,一般需要为每个目标定义不同的回调方法而不是在update方法中来区分;
3) 观察者模式有推模型和拉模型两种方式;
@@@模式的优点:
1) 观察者模式实现了观察者和目标对象之间的抽象耦合;
2) 观察者模式实现了动态联动;
3) 观察者模式支持广播通信;
@@@模式的缺点:
1) 可能会引起无谓的操作;
@@@模式的本质:
触发联动
@@@模式体现的设计原则:
NA
09年5月CSDN一网友提出如下问题:
设计一个用于管理银行客户的类BankCustomer: 仅描述客户的几个重要方面: 帐号、身份证号、姓名、联系方式、密码、账户余额。 所有的成员变量均用private访问控制,因此每一个成员变量就要有相应的存取器 (getter和setter,即获取和设置其值的相应的成员方法。需要setter还是getter,还是两者都要,视情况而定) 成员方法: 开户(开户时必须要有身份证号),系统自动生成帐号,帐号使用系统时间(格式:"yyyyMMddHHmmss"14位),初始密码为“666666”。 注意开户和构造方法之间的关系。 存钱、取钱、显示账户信息、修改密码(密码最短要六位) 怎样在main中使用这个类,自行安排,要表现出你设计的类的各个方面,并在main中用英语加以注释
根据此,使用工厂模式设计如下若干类:
------------factory部分---------------------------xiaobin-------------
Customer: 抽象类(factory祖先类)
BankCustomer:继承类(factory类)
〉〉〉〉〉〉〉〉〉〉扩展部分
ContactWay:联系方式(factory引用类)
IM :实时消息(ContactWay引用类)
------------product部分---------------------------xiaobin-------------
Bank: 接口(product接口)
Account: 实现类(concrete product类)
类图如下:(使用Enterprise Architect绘制)
使用EA生成的代码如下:
Customer.java
package bankCustomer; /** * @author xiaobin * @version 1.0 * @created 23-5-2009 15:19:06 */ public abstract class Customer { private Bank bank; private double myMoney; public Bank m_Bank; public Customer(){ } public void finalize() throws Throwable { } public abstract Bank createAccount(); public void myAccount(){ } public void mySaveMoney(){ } public void myTakeMoney(){ } }
BankCustomer.java
package bankCustomer; /** * @author xiaobin * @version 1.0 * @created 23-5-2009 15:30:13 */ public class BankCustomer extends Customer { private ContactWay contact; private String name; private String personID; public Account m_Account; public ContactWay m_ContactWay; public BankCustomer(){ } public void finalize() throws Throwable { super.finalize(); } public Bank createAccount(){ return null; } private ContactWay getContact(){ return null; } private String getName(){ return ""; } private String getPersonID(){ return ""; } /** * * @param contactWay */ private void setContact(ContactWay contactWay){ } /** * * @param myName */ private void setName(String myName){ } /** * * @param myPersonID */ private void setPersonID(String myPersonID){ } }
ContactWay.java
package bankCustomer; /** * @author xiaobin * @version 1.0 * @created 23-5-2009 15:30:29 */ class ContactWay { private String email; private IM imContact; private String mobilePhone; private String phone; public IM m_IM; public ContactWay(){ } public void finalize() throws Throwable { } private String getEmail(){ return ""; } private IM getImContact(){ return null; } private String getMobilePhone(){ return ""; } private String getPhone(){ return ""; } /** * * @param myEmail */ private void setEmail(String myEmail){ } /** * * @param im */ private void setImContact(IM im){ } /** * * @param mobilePhone */ private void setMobilePhone(String mobilePhone){ } /** * * @param myPhone */ private void setPhone(String myPhone){ } }
IM.java
package bankCustomer; /** * @author xiaobin * @version 1.0 * @created 23-5-2009 15:30:39 */ class IM { private String MSN; private String QQ; public IM(){ } public void finalize() throws Throwable { } private String getMSN(){ return ""; } private String getQQ(){ return ""; } /** * * @param msn */ private void setMSN(String msn){ } /** * * @param qq */ private void setQQ(String qq){ } }
Bank.java
package bankCustomer; /** * @author xiaobin * @version 1.0 * @created 23-5-2009 15:19:16 */ public interface Bank { /** * * @param money */ public boolean saveMoney(double money); /** * * @param money */ public boolean takeMoney(double money); }
Account.java
package bankCustomer; /** * @author xiaobin * @version 1.0 * @created 07-7-2013 20:21:35 */ public class Account implements Bank { private String accountID; private double accountSurplus; private Date date; public Account(){ } public void finalize() throws Throwable { } /** * * @param val1 * @param val2 */ private double addAlgorithm(double val1, double val2){ return 0; } /** * * @param now */ private String createMyAccount(Date now){ return ""; } private String getAccountID(){ return ""; } /** * * @param oldPWD * @param curPWD */ private boolean modifyPWD(String oldPWD, String curPWD){ return false; } /** * * @param money */ public synchronized boolean saveMoney(double money){ return false; } private void setAccountID(){ } /** * * @param val1 * @param val2 */ private double subAlgorithm(double val1, double val2){ return 0; } /** * * @param money */ public synchronized boolean takeMoney(double money){ return false; } }
当网站做到一定规模的时候,web单个页面需要涉及到的业务也会越来越多,每个页面可能会向后端发起几个、十几个甚至几十个请求。对于java、python之类的支持多线程的语言可以使用多线程编程,但也会增加程序的复杂性,像php这样的不支持多线程的语言只能借助其他方法实现并行,下面总结几种比较实用的并行化框架。
1、yar 是鸟哥开发的一个 基于php扩展的RPC框架。
//service.php class ServiceTest { public function test($param){ sleep(1); return 'sleep 1s'; } public function test2($param){ sleep(1); return 'sleep 1s'; } public function test3($param){ sleep(1); return 'sleep 1s'; } } $service = new Yar_Server(new ServiceTest()); $service->handle(); //client.php $api = "http://127.0.0.1/yar/service.php"; $param = array(1,2,3); function callback($retval, $callinfo){ print_r($retval); } Yar_Concurrent_Client::call($api, 'test', array($param), 'callback'); Yar_Concurrent_Client::call($api, 'test2', array($param), 'callback'); Yar_Concurrent_Client::call($api, 'test3', array($param), 'callback'); Yar_Concurrent_Client::loop();
上面服务端代码有3个方法都sleep一秒来模拟业务端的处理,通过yar扩展注册服务,client端通过Yar_Concurrent_Client并行请求这个三个方法,最终执行时间是大约是1s。值得一提yar的并行操作是通过libcurl的并行实现的,服务端代码必须能够通过http访问到。对于tpc和unix socket目前只能进行同步请求,如需要并行实现需要自行加入消息队列之内的东西去实现。
2、APS,是安居客集团以zmq为消息中间件,以事件驱动进行网来请求的一个跨语言的RPC框架,框架中有一个代理(device)监听两个端口或者socket文件,分别监听客户端发来的请求和转发给服务端的多个worker进程,并负责把woker处理的返回的数据转发到客户端。运行github上面用php写的demon代码如下。
3、Gearman,是一个用来把工作委派给其他机器、分布式的调用更适合做某项工作的机器、并发的做某项工作在多个调用间做负载均衡、或用来在调用其它语言的函数的系统。通过worker向Gearmand守护进程注册工作,客户端通过Gearmand将任务分派到后端的worker进程,具体实现和APS类似。
4、nodejs,是一个事件驱动的单进程语言,可以通过这种异步编程模式实现对后台业务的并行处理。下面demo是以nodejs为客户端请求php后端的一个耗时3s的方法,一个耗时2s的方法:
var http = require("http"); var url = require('url'); var eventProxy = require('eventproxy'); var handle = {}; handle['/'] = test; function start(route, handle) { function onRequest(request, response) { var pathname = url.parse(request.url).pathname; route(handle, pathname, response); } http.createServer(onRequest).listen(8081); } function route(handle, pathname, response){ console.log("route\n"); if (typeof handle[pathname] === 'function') { handle[pathname](response); } else { console.log(pathname + 'is no fund'); } } function test(response) { var ep = new eventProxy(); ep.all('data1', 'data2', function(a, b){ response.writeHead(200, {"Content-Type": "text/plain"}); response.write(a); response.write(b); response.end(); }); http.get('http://127.0.0.1/nodejs/service.php?function=test', function(data){ var buffers = [], size = 0; data.on('data', function(buffer) { buffers.push(buffer); size += buffer.length; }); data.on('end', function(){ var buffer = new Buffer(size), pos = 0; for(var i = 0, l = buffers.length; i < l; i++) { buffers[i].copy(buffer, pos); pos += buffers[i].length; } ep.emit('data1', buffer); }); }); http.get('http://127.0.0.1/nodejs/service.php?function=test2', function(data){ var buffers = [], size = 0; data.on('data', function(buffer) { buffers.push(buffer); size += buffer.length; }); data.on('end', function(){ var buffer = new Buffer(size), pos = 0; for(var i = 0, l = buffers.length; i < l; i++) { buffers[i].copy(buffer, pos); pos += buffers[i].length; } ep.emit('data2', buffer); }); }); } function sleep(milliSeconds) { var startTime = new Date().getTime(); while (new Date().getTime() < startTime + milliSeconds); } start(route, handle);
总结:
上述并行请求的实现有两种方式,一是基于事件驱动模型nodejs、yar(yar底层libcurl的curl_multi应用select()),二是基于消息队列的多进程的任务调度APS、Gearman。在实际的应用中的选择什么样的并行框架可能会根据各个方面来抉择,不管选择哪个,带来的一个很大的好处是使程序SOA化,减小代码间的耦合度,更变方便扩展。