您的位置:首页 > 编程语言 > PHP开发

php 面向对象开发中的观察者模式介绍

2012-05-21 17:32 771 查看
观察者模式可以通过灵巧的方式减少各个组件之间的联系。使各个类专注于做自己的事情。在类的扩展功能需要调整修改时而不用频繁的修改主流程代码。比如一个用户注册后需要产生的相关动作:如发送短信,发送邮件,写文本日志等的需求。下面我们通过一个模拟案例来演示SPL在实现Observer设计模式上的威力。该案例模拟了一个网站的用户管理模块,该模块包括3个主要功能:新增1个用户把指定用户的密码变更为他所指定的新密码在用户忘记密码时重置其密码每当这些功能完成后,都需要将密码告知用户。除了传统的向用户发送Email这种手段外,我们还需要向用户的手机发送短信,让他们更加方便地知道密码是什么。假设我们的网站还有一套站内的消息系统,我们称之为小纸条,在用户变更或重置密码后,向他们发送小纸条会令他们高兴的。经过分析,该案例适合使用Observer设计模式解决,因为将密码告知用户的多种手段与用户密码的改变——无论是从无到有,用户主动变更,还是系统重置——形成了多对一的关系。我们决定定义一个User类表示用户,实现需求中的3个功能。该类就是Observer设计模式中的目标(Subject)角色。我们还需要一组类,实现利用各种手段向用户发送新密码的功能,这些类就充当了Observer设计模式中的观察者(Observer)角色。经过简单地分析后,我们画出UML类图:图1.模拟案例的UML类图根据UML类图,首先,定义1个名为User的类模拟案例中的用户。尽管实际网站中的用户要有更多的属性,特别是通常需要用ID来标识每个用户,但是我们为了突出本文的主题,只保留了案例所需的属性。清单2.User类的源代码
<?php
classUserimplementsSplSubject{
private$email;
private$username;
private$mobile;
private$password;
private$observers=NULL;
publicfunction__construct($email,$username,$mobile,$password)
{
$this->email=$email;
$this->username=$username;
$this->mobile=$mobile;
$this->password=$password;
$this->observers=newSplObjectStorage();
}
	publicfunctionattach(SplObserver$observer)
{
		$this->observers->attach($observer);
}
	publicfunctiondetach(SplObserver$observer)
{
		$this->observers->detach($observer);
}
	publicfunctionnotify()
{
		$userInfo=array('username'=>$this->username,'password'=>$this->password,'email'=>$this->email,'mobile'=>$this->mobile,);
		foreach($this->observersas$observer)
	{
			$observer->update($this,$userInfo);
}
}
	publicfunctioncreate()
{
		echo__METHOD__,PHP_EOL;
		$this->notify();
}
	publicfunctionchangePassword($newPassword)
{
		echo__METHOD__,PHP_EOL;
		$this->password=$newPassword;$this->notify();
}
	publicfunctionresetPassword()
{
		echo__METHOD__,PHP_EOL;
		$this->password=mt_rand(100000,999999);
		$this->notify();}
}
User类要想充当目标角色,就需要实现
SplSubject
接口,而按照实现接口的法则,
attach()
detach()
notify()
就必须被实现。请注意,由于在
SplSubject
接口中,
attach
()和
detach
()的参数都使用了类型提示(
typehinting
),在实现这两个方法时,也
不能
省略参数前面的类型。
我们还使用了
$
observers
实例属性保存一个
SplObjectStorage
对象,用来存放所有注册上来的观察者。的确,一个数组就能解决问题,但是很快就可以发现,使用了
SplObjectStorage
之后删除一个观察者实现起来是多么简单,直接委托给
SplObjectStorage
对象!是的,不需要再使用最原始的
for
语句遍历观察者数组或者使用
array_search
函数,1行搞定。接下来分别定义充当观察者角色的3个信息发送类。为了简单,我们只是通过输出文本来假装发送信息。可即使是假装,依然需要知道用户的信息。可看看
SplObserver
接口
update
()
方法的签名,多么令人沮丧,它无法接受目标角色通过调用其
notify
()方法发送通告时给出的参数。如果你试图在重写
update
()
方法时加上第2个参数,会得到一个类似Fatalerror:DeclarationofEmailSender::update()mustbecompatiblewiththatofSplObserver::update()的错误而使代码执行终止。其实,当目标所持有的状态(在本例中是用户的密码)更新时,如何通知观察者有两种方法。“拉”的方法和“推”的方法。SPL使用的是“拉”的方法,观察者需要通过目标的引用(作为
update()
方法的参数传入)来访问其属性。“拉”的方法需要让观察者更了解目标都拥有哪些属性,这增加了它们耦合度。而且主题也要对观察者门户大开,违背了封装性。解决的方法是在目标中提供一系列getter方法,如
getPassword()
来让观察者获得用户的密码。虽然“拉”的方法可能被认为更加正确,但是我们觉得让主题把用户的信息“推”过来更加方便。既然通过在重写
update
()
方法时加上第2个参数是行不通的,那么就从别的方向上着手。好在PHP在方法调用上有这样的特性,只要给定的参数(实参)不少于定义时指定的必选参数(没有默认值的参数),PHP就不会报错。传入一个方法的参数个数,可以通过
func_num_args
()函数获取;
多余的参数可以使用
func_get_arg
()
函数读取。注意该函数是从0开始计数的,即0表示第1个实参。利用这个小技巧,
update
()
方法可以通过
func_get_arg(1)
接收一个用户信息的数组,有了这个数组,就能知道邮件该发给谁,新密码是什么了。为了节约篇幅,而且三个信息发送类非常相像,下面只给出其中一个的源代码,完整的源代码可以下载本文的附件得到。清单3.Email_Sender类的源代码
<?php
	classEmailSenderimplementsSplObserver
{
	publicfunctionupdate(SplSubject$subject)
{
		if(func_num_args()===2)
	{
			$userInfo=func_get_arg(1);
			echo"向{$userInfo['email']}发送电子邮件成功。内容是:你好{$userInfo['username']}"."你的新密码是{$userInfo['password']},请妥善保管",PHP_EOL;
}
}
}
最后我们写一个测试脚本
test.php
。建议使用CLI的方式
php–ftest.php
来执行该脚本,但由于设置了
Content-Type
响应头部字段为
text/plain
,在浏览器中应该也能看到一行一行显示的结果(因为没有用
<br/>
做换行符而是使用常量
PHP_EOL
,所以不设置
Content-Type
的话,就不能正确分行显示了)。清单4.用于测试的脚本
<?php
	header('Content-Type:text/plain');
	function__autoload($class_name)
{
		require_once"$class_name.php";
}
	$email_sender=newEmailSender();
	$mobile_sender=newMobileSender();
	$web_sender=newWebsiteSender();
	$user=newUser('user1@domain.com','张三','13610002000','123456');
	//创建用户时通过Email和手机短信通知用户
	$user->attach($email_sender);
	$user->attach($mobile_sender);
	$user->create($user);echoPHP_EOL;//用户忘记密码后重置密码,还需要通过站内小纸条通知用户
	$user->attach($web_sender);
	$user->resetPassword();
	echoPHP_EOL;//用户变更了密码,但是不要给他的手机发短信
	$user->detach($mobile_sender);
	$user->changePassword('654321');
	echoPHP_EOL;
清单5.运行结果
User::create向user1@domain.com发送电子邮件成功。内容是:你好张三你的新密码是123456,请妥善保管
	向13610002000发送短消息成功。内容是:你好张三你的新密码是123456,请妥善保管
User::resetPassword向user1@domain.com发送电子邮件成功。内容是:你好张三你的新密码是363989,请妥善保管
	向13610002000发送短消息成功。内容是:你好张三你的新密码是363989,请妥善保管
	这是1封站内小纸条。你好张三,你的新密码是363989,请妥善保管
User::changePassword向user1@domain.com发送电子邮件成功。
	内容是:你好张三你的新密码是654321,请妥善保管这是1封站内小纸条。你好张三,你的新密码是654321,请妥善保管
我们看到,用户
张三
可以通过多种手段知道他的密码是什么。来自91CTO
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: