登录系统想必大家都做过,验证用户名密码就登录成功,日志系统应该记录此次登录,如果登录出错,安全系统应该会记录此次错误,邮件系统也应该会发送相关邮件给管理员,等等。这就好像登录系统被很多人监视一样,一旦有什么风吹草动,立即会被其它系统获悉。那就用观察者模式来试试,类图如下:
很简单的模式,实现代码:
<?php interface Observable{ function attach( Observer $observer ); function detach( Observer $observer ); function notify(); } class login implements Observable{ const LOGIN_USER_UNKNOW = 1; const LOGIN_WRONG_PASS = 2; const LOGIN_ACCESS = 3; private $status = array(); private $observers = array(); public function setStatus( $status, $user, $ip ) { $this->status = array( $status, $user, $ip ); } public function getStatus() { return $this->status; } public function handleLogin( $user, $pass, $ip ) { switch ( mt_rand( 1, 3 ) ) { case 1: $this->setStatus( self::LOGIN_USER_UNKNOW, $user, $ip ); $ret = false; break; case 2: $this->setStatus( self::LOGIN_WRONG_PASS, $user, $ip ); $ret = false; break; case 3: $this->setStatus( self::LOGIN_ACCESS, $user, $ip ); $ret = true; break; } $this->notify(); return $ret; } public function attach( Observer $observer ) { $this->observers[] = $observer; } public function detach( Observer $observer ) { $newObservers = array(); foreach ( $this->observers as $obs ) { if ( $obs !== $observer ) $newObservers[] = $obs; } $this->observers = $newObservers; } public function notify() { foreach ( $this->observers as $obs ) { $obs->update( $this ); } } } interface Observer{ function update( Observable $observable ); } class SecurityMonitor implements Observer{ function update( Observable $observable ) { $status = $observable->getStatus(); if($status[0] == Login::LOGIN_WRONG_PASS){ echo __CLASS__.":".$status[1]."于".$status[2]."登录失败"; } } } $login = new Login(); $login->attach(new SecurityMonitor()); $login->handleLogin('XXX','XXX','127.0.0.1'); ?> 出错时的运行结果: SecurityMonitor:XXX于127.0.0.1登录失败[Finished in 0.1s]
代码中可以看到login对象主动添加SecurityMonitor对象观察。这样要调用Login::getStatus(),SecurityMonitor类就必须了解更多信息。虽然调用发生于一个ObServable对象上,但无法保证对象也是一个Login对象。为解决这个问题,有一个办法:断续保持ObServable接口的通用性,由ObServer类负责保证它们的主体是正确的类型。它们甚至能将自己添加到主体上。类图如下:
实现代码如下:
<?php interface Observable{ function attach( Observer $observer ); function detach( Observer $observer ); function notify(); } class login implements Observable{ const LOGIN_USER_UNKNOW = 1; const LOGIN_WRONG_PASS = 2; const LOGIN_ACCESS = 3; private $status = array(); private $observers = array(); public function setStatus( $status, $user, $ip ) { $this->status = array( $status, $user, $ip ); } public function getStatus() { return $this->status; } public function handleLogin( $user, $pass, $ip ) { switch ( mt_rand( 1, 3 ) ) { case 1: $this->setStatus( self::LOGIN_USER_UNKNOW, $user, $ip ); $ret = false; break; case 2: $this->setStatus( self::LOGIN_WRONG_PASS, $user, $ip ); $ret = false; break; case 3: $this->setStatus( self::LOGIN_ACCESS, $user, $ip ); $ret = true; break; } $this->notify(); return $ret; } public function attach( Observer $observer ) { $this->observers[] = $observer; } public function detach( Observer $observer ) { $newObservers = array(); foreach ( $this->observers as $obs ) { if ( $obs !== $observer ) $newObservers[] = $obs; } $this->observers = $newObservers; } public function notify() { foreach ( $this->observers as $obs ) { $obs->update( $this ); } } } interface Observer{ function update( Observable $observable ); } //以上代码和上例是一样的 abstract class LoginObserver implements Observer{ private $login; public function __construct( Login $login ) { $this->login = $login; $login->attach( $this ); } public function update( Observable $observable ) { if ( $this->login === $observable ) $this->doUpdate( $observable ); } abstract function doUpdate( Login $login ); } class SecurityMonitor extends LoginObserver{ public function doUpdate( Login $login ) { $status = $login->getStatus(); if ( $status[0] == Login::LOGIN_WRONG_PASS ) echo __CLASS__.":".$status[1]."于".$status[2]."登录失败"; } } $login = new Login(); new SecurityMonitor($login);//此外login对象是被动被观察的 $login->handleLogin( 'XXX', 'XXX', '127.0.0.1' ); ?> 运行结果与上例子相同
在php5后,内置的SPL扩展提供了对观察者模式的原生支持。将上例子通过SPL改进后:
<?php class login implements SplSubject{ const LOGIN_USER_UNKNOW = 1; const LOGIN_WRONG_PASS = 2; const LOGIN_ACCESS = 3; private $status = array(); // private $observers = array(); private $storage; public function __construct() { $this->storage = new SplObjectStorage(); } public function setStatus( $status, $user, $ip ) { $this->status = array( $status, $user, $ip ); } public function getStatus() { return $this->status; } public function handleLogin( $user, $pass, $ip ) { switch ( mt_rand( 1, 3 ) ) { case 1: $this->setStatus( self::LOGIN_USER_UNKNOW, $user, $ip ); $ret = false; break; case 2: $this->setStatus( self::LOGIN_WRONG_PASS, $user, $ip ); $ret = false; break; case 3: $this->setStatus( self::LOGIN_ACCESS, $user, $ip ); $ret = true; break; } $this->notify(); return $ret; } public function attach( SplObserver $observer ) { $this->storage->attach( $observer ); } public function detach( SplObserver $observer ) { $this->storage->detach( $observer ); } public function notify() { foreach ( $this->storage as $obs ) { $obs->update( $this ); } } } abstract class LoginObserver implements SplObserver{ private $login; public function __construct( Login $login ) { $this->login = $login; $login->attach( $this ); } public function update( SplSubject $subject ) { if ( $this->login === $subject ) $this->doUpdate( $subject ); } abstract function doUpdate( Login $login ); } class SecurityMonitor extends LoginObserver{ public function doUpdate( Login $login ) { $status = $login->getStatus(); if ( $status[0] == Login::LOGIN_WRONG_PASS ) echo __CLASS__.":".$status[1]."于".$status[2]."登录失败"; } } $login = new Login(); new SecurityMonitor( $login ); $login->handleLogin( 'XXX', 'XXX', '127.0.0.1' ); ?>
代码都写完了,还是要懂点理论的。
观察者模式的定义
定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。观察者模式由四种角色构成:
1、Subject被观察者
定义被观察者必须实现的职责,它必须能够动态地增加、取消观察者。它一般是抽象类或者是实现类,仅仅完成作为被观察者必须实现的职责,管理观察者并通过观察者。
2、Observer观察者
观察者接收到消息后,即进行update操作,对接收到的信息进行处理。
3、ConcreteSubject具体的被观察者
定义被观察者自己的业务逻辑,同时定义对哪些事件进行通知。
4、ConcreteObserver具体的观察者
每个观察在接收到消息后的处理反应是不同,各个观察者有自己的处理逻辑。
观察者模式的优点
1、观察者和被观察者之间是抽象耦合
如此设计,则不管是增加观察者还是被观察者都非常容易扩展,而且在java、php中都已经实现的抽象层级的定义,在系统扩展方面更是得心应手。
2、建立一套触发机制
根据单一职责原则,每个类的职责是单一的,那么怎么把各个单一的职责串联成真实世界的复杂的逻辑关系呢?观察者模式可以完美地实现这里的链条形式
观察者模式的缺点
观察者模式需要考虑一下开发效率和运行效率的问题,一个被观察者,多个观察者,开发和调试就会比较复杂,而且在php中消息的通知是顺序执行,一个观察者卡壳,会影响整体的执行效率。在这种情况下,一般多考虑异步的方式。多级触发时的效率更是让人担忧,设计时注意。
观察者模式的使用场景
1、关联行为场景。需要注意的是,关系行为是可拆分的,而不是“组合”关系
2、事件多级触发场景
3、跨系统的消息交换场景,如消息队列的处理机制
相关推荐
Redis安全通信知识点总结: 1. Redis安全通信的必要性: 当应用部署在A机房,而存储部署在B机房时,若需跨机房访问Redis数据,普通TCP直接访问会导致数据在公网上裸露传输,存在被窃听的风险。...
6. 平面镜成像:物体在平面镜中成像的大小与物体本身大小有关,与平面镜的大小和观察者的位置无关,因此答案是C。 7. 光的折射与视觉错觉:透过装水的玻璃杯看物体,字的形状会因光的折射而改变,与玻璃杯的种类、...
多普勒效应是指波源与观察者相对运动时,观察到的波长或频率发生变化的现象。 2. **声波的干涉和衍射**:第二题提到的“隔墙有耳”是声波的衍射现象,即波绕过障碍物传播;而“在水里的人能听到岸上的声音”是声波...
题目中,“潭清疑水浅”是因为光从水底折射到空气中,使得观察者觉得水底变浅了。 3. 凸透镜成像规律:根据题目描述,物体从2倍焦距外向焦点移动时,像会逐渐变大,像距也会逐渐变大,因此答案是C。 4. 物态变化:...
2. 明显衍射的条件:要观察到明显的衍射效果,障碍物或缝隙的尺寸必须与波长相当或小于波长。当缝隙宽度远大于波长时,衍射效应不明显,波将像直线传播一样通过障碍物。 3. 波的独立传播特性:两列或多列波相遇时,...
1. 光的折射与反射:题目中的筷子在水中“弯折”是光的折射现象,光线从空气进入水时发生折射,导致观察者误以为筷子的位置发生了改变。景色的倒影是平面镜成像,遵循反射定律。雨后彩虹则是光的色散现象,即太阳光...
2. **参照物选择**:在判断物体运动状态时,通常选取相对于观察者位置发生变化的其他物体作为参照物。例如,“月亮在云中穿行”的参照物是云。 3. **长度单位**:日常生活中的铅笔长度通常在20cm左右,因此未用过的...
【知识点详解】 这篇文档是针对八年级物理上册的...通过这份教案,教师可以引导学生深入理解声音的基本性质,培养他们的观察、分析和实验能力,同时也为学生提供了实际操作和思考的机会,增强对物理知识的直观感受。
5. 信息安全:"隔墙有耳"和"没有不透风的墙"描述了信息的扩散性(D),即信息容易传播和扩散,难以完全保密。 6. U/C矩阵:完备性检验、一致性检验和无冗余性检验是U/C矩阵正确性检验的组成部分,但不包括准确性...
7. **声音的分类**:成语中“隔墙有耳”表示固体能传声,“掩耳盗铃”是关于听觉的误解,“低声细语”和“震耳欲聋”描述的是声音的响度。 8. **声波图分析**:声波图的形状可以反映声音的特性,例如波形的高度代表...
C选项错误地断言所有相对运动都会产生多普勒效应,事实上只有观察者与波源之间有相对速度时才会出现。D选项错误地将超声波测速解释为波的干涉原理,实际上利用的是多普勒效应。 4. 声波干涉与波的强弱变化:第四题...
- 音色:每个物体发出的声音都有独特的音色,是由物体振动的复杂模式决定的,例如“闻其声而知其人”。 - 响度:声音的强弱,与振幅和距离声源的距离有关。 2. 声音的传播 - 声音可以在固体、液体和气体中传播,...
【声音的传播】是小学三年级科学课程中的一个重要知识点,它主要探讨了声音如何从声源传递到接收者,以及声音传播的介质。在大象版(新教材)三年级上册的小学科学教学课件中,这一主题被详细地展开,旨在帮助学生...
在生活中,声音的“隔墙有耳”效应和无线电波穿透建筑物传播都是衍射的实例。 波的干涉是基于波的叠加原理,即在任意一点,所有波到达的振幅相加。当两个波源的相位差为零或整数倍的波长时,它们在某点的叠加结果会...
8. 观察微小振动的方法:为了观察到桌子的微小振动,可以添加粉末或细沙在桌子上,当桌子振动时,粉末或细沙的移动会更明显地显示出振动。 9. 音调在生活中的应用:通过听灌水时声音的变化(音调升高)来判断暖水瓶...
- 含有 "遥闻深巷" 的成语可能有 "隔墙有耳",表示秘密谈话可能会被人听到。 - 含有 "群响毕绝" 的成语可能有 "万籁俱寂",形容周围非常安静,没有任何声响。 - 含有 "几欲先走" 的成语可能有 "落荒而逃",形容因...
1. 声音的传播:题目中提到“隔墙有耳”,这说明声音能够在固体中传播,不只是空气。声音的传播需要介质,固体、液体和气体都可以作为传播媒介。 2. 噪声控制:工作人员佩戴耳罩的头盔是为了在人耳处减弱噪声,防止...
16. 寒暑表的分度值和读数需要具体观察图表来填写。 17. 体温计是根据液体的热胀冷缩规律制成,其读数需要看清楚刻度。 18. 控制噪声可以从声源处、传播途径和接收者三方面进行,调小音量是在声源处减弱噪声,同时...
不同乐器的音色是由其特有的振动模式决定的,因此可以根据音色来识别乐器。 6. 减弱噪声的方法:自由大桥的爆破采用微爆破技术减小声音,这是在声源处减弱噪声,控制的是声音的响度。 7. 声音的传播和特性:声音...
图1的实验展示了声音由物体振动产生,并能在空气中传播,而在月球上没有空气,所以不能观察到乒乓球被弹开的现象,因为声音无法在真空中传播。 2. 题目提到了声音的传播介质,如水和空气,以及噪声的产生和减弱。...