课程名称:程序设计方法学
实验5:OOP设计模式-行为型模式的应用与实现
时间:2015年12月02日三,第3、4节地点:理
一、实验目的
加深对行为型设计模式的理解以及在开发中的实际应用能力。
二、实验内容
实验内容:
在工业自动化系统中,作为监控端,要求能够实时采集生产线上每一个测点的数据,并根据事先设定好的条件来发出告警。目前,系统可以支持的告警方式在形式上呈现多样化的趋势,主要包括屏幕显示告警、语音发声告警、电话拨号告警、电子邮件告警、手机短信告警以及其他可利用的告警方式等。在设置监控端时,用户可以根据实际需要,针对生产线上的每个测点,动态地设定与其相关联的多个告警。每个告警都有对应的告警方式和告警对象。当从某个测点返回的采集数据超过了事先设定好的阈值时,就要立即触发与该测点相关的所有告警,以便相关人员能够及时得到有关异常情况的通知,便于其进行人工干预和处理。
针对以上说明,现在要求你设计监控端软件中的告警功能部分。要求使该系统能够实现针对测点和告警间的一对多的动态组合,并具有较强的可扩展性。
要求:
实验报告中要求绘制UML类图,给出设计中各个类的主要成员,并附以适当的文字说明详细描述每个类的作用;
实验报告中应针对上述设计,给出使用C++(或java)实现的完整的示意性代码,以及在本地计算机上调试、运行该程序的截图(要求截图的结果中能体现个人的学号、姓名等信息)。
实验报告的末尾请对所用的设计模式、该模式的优缺点及使用心得等做简要小结。
附加要求:
对于我们所介绍的常见的几种行为型的设计模式,你能不能也举出这些模式的一些应用实例,并给出相关的UML类图说明呢?
三、实验环境
硬件条件:微机
操作系统:Windows 2007
开发环境:Eclipse,Rational Rose 2003
四、实验步骤和结果
(一)选择适当的面向对象设计模式
由题意,要求设计一个设计监控端软件中的告警功能,作为监控端,要求能够实时采集生产线上每一个测点的数据,并根据事先设定好的条件来发出告警。告警方式有多样,当从某个测点返回的采集数据超过了事先设定好的阈值时,就要立即触发与该测点相关的所有告警。这需要实现测点和告警间的一对多的动态组合。故选用的是行为型设计模式中的观察者模式。观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己,并做出相应动作。
(二)UML类图的设计和绘制
设计分析:
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,所有依赖于它的对象都得到通知并被自动更新。观察者模式中的角色有:
1、 抽象主题(Subject)角色:抽象主题角色把所有对观察者对象的引用保存在一个聚集(比如ArrayList对象)里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象,抽象主题角色又叫做抽象被观察者(Observable)角色。
2、 具体主题(ConcreteSubject)角色:将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色又叫做具体被观察者(Concrete Observable)角色。
3、 抽象观察者(Observer)角色:为所有的具体观察者定义一个接口,在得到主题的通知时更新自己,这个接口叫做更新接口。
4、 具体观察者(ConcreteObserver)角色:存储与主题的状态自恰的状态。具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。如果需要,具体观察者角色可以保持一个指向具体主题对象的引用。
(图1 一般的观察者模式的UML类图 )
(图2该监控端软件中的告警功能的观察者模式的UML类图)
(三)针对上述设计,用java实现的完整的示意性代码如下所示:
Java 类的建立如下所示:
1、Sub_MeasuringPoint抽象类,即测点抽象类,该目标抽象类提供注册和删除观察者对象的接口:
1 package com.shen.observer; 2 3 import java.util.ArrayList; 4 5 //目标类: 6 //目标知道它的观察者,可以有任意多个观察者观察同一目标 7 //提供注册和删除观察者对象的接口 8 9 public abstract class Sub_MeasuringPoint { 10 11 12 //用来保存注册的观察者对象 13 private ArrayListlist=new ArrayList (); 14 15 //注册观察者对象 16 public void Attach(Observer observer) { 17 18 list.add(observer); 19 System.out.println("Attached an observer successfully!"); 20 } 21 22 //删除观察者对象 23 public void Detach(Observer observer) { 24 25 list.remove(observer); 26 System.out.println("Detached an observer successfully!"); 27 } 28 29 //通知所有注册的观察者对象 30 public void NotifyObservers(String newState) { 31 32 for (Observer observer:list) { 33 34 observer.Update(newState); 35 observer.AlarmMode(); 36 } 37 } 38 }
2、ConSub_MeasuringPoint具体目标类,当从某个测点返回的采集数据超过了事先设定好的阈值时,就要立即触发与该测点相关的所有告警
1 package com.shen.observer; 2 3 //具体目标类 4 public class ConSub_MeasuringPoint extends Sub_MeasuringPoint { 5 6 private String state; 7 public String getState() { 8 return state; 9 } 10 11 //当从某个测点返回的采集数据超过了事先设定好的阈值时,就要立即触发与该测点相关的所有告警, 12 //数据超过阈值 13 14 public void DataOverThreshold(String newState) { 15 16 state=newState; 17 System.out.println(">>>>>当前检测到测点状态为:"+state); 18 19 状态发生改变,通知各个观察者 20 this.NotifyObservers(state); 21 22 } 23 }
3、Observer 观察者接口,为那些在目标发生时需获得通知的对象定义一个更新接口:
package com.shen.observer; //这是一个观察者接口,为那些在目标发生时需获得通知的对象定义一个更新接口 public interface Observer { public void Update(String state);//检查到的状态更新 public Object alarmObject=new Object();//告警对象 public void AlarmMode();//告警方式 }
4、Ob_ScreenDisplayAlarm 屏幕显示告警:
package com.shen.observer; //屏幕显示告警 public class Ob_ScreenDisplayAlarm implements Observer{ //观察者的状态 @SuppressWarnings("unused") private String observerState; @Override public void Update(String state) { // TODO Auto-generated method stub observerState=state; System.out.print("[屏幕显示告警器]发现测点数据超过阈值"); } @Override public void AlarmMode() { // TODO Auto-generated method stub System.out.println(",于是触发【屏幕显示告警】!"); } }
5、Ob_VoiceAudibleAlarm语音发声告警:
package com.shen.observer; //语音发声告警 public class Ob_VoiceAudibleAlarm implements Observer{ //观察者的状态 @SuppressWarnings("unused") private String observerState; @Override public void Update(String state) { // TODO Auto-generated method stub observerState=state; System.out.print("[语音发声告警器]发现测点数据超过阈值"); } @Override public void AlarmMode() { // TODO Auto-generated method stub System.out.println(",于是触发【语音发声告警】!"); } }
6、Ob_TelephoneDialingAlarm电话拨号告警:
package com.shen.observer; //电话拨号告警 public class Ob_TelephoneDialingAlarm implements Observer{ //观察者的状态 @SuppressWarnings("unused") private String observerState; @Override public void Update(String state) { // TODO Auto-generated method stub observerState=state; System.out.print("[电话拨号告警器]发现测点数据超过阈值"); } @Override public void AlarmMode() { // TODO Auto-generated method stub System.out.println(",于是触发【电话拨号告警】!"); } }
7、Ob_EmailAlarm 电子邮件告警
package com.shen.observer; //电子邮件告警 public class Ob_EmailAlarm implements Observer { //观察者的状态 @SuppressWarnings("unused") private String observerState; @Override public void Update(String state) { // TODO Auto-generated method stub observerState=state; System.out.print("[电子邮件告警器]发现测点数据超过阈值"); } @Override public void AlarmMode() { // TODO Auto-generated method stub System.out.println(",于是触发【电子邮件告警】!"); } }
8、Ob_SMSAlarm 手机短信告警
package com.shen.observer; //手机短信告警 public class Ob_SMSAlarm implements Observer{ //观察者的状态 @SuppressWarnings("unused") private String observerState; @Override public void Update(String state) { // TODO Auto-generated method stub observerState=state; System.out.print("[手机短信告警器]发现测点数据超过阈值"); } @Override public void AlarmMode() { // TODO Auto-generated method stub System.out.println(",于是触发【手机短信告警】!"); } }
9、Ob_OtherAvailableAlarm其他可利用的告警方式
package com.shen.observer; //其他可利用的告警方式 public class Ob_OtherAvailableAlarm implements Observer{ //观察者的状态 @SuppressWarnings("unused") private String observerState; @Override public void Update(String state) { // TODO Auto-generated method stub observerState=state; System.out.print("[其他可利用的告警方式]发现测点数据超过阈值"); } @Override public void AlarmMode() { // TODO Auto-generated method stub System.out.println(",于是触发其他可利用的告警方式......"); } }
(四)编写监控器测试代码(MonitorClientTest.java)如下所示:
package com.shen.observer; //客户端测试类 public class MonitorClientTest { public static void main(String[] args) { // 创建主题对象,即测点 System.out.println("--------<监控端软件中的告警功能_(shen)>-------"); ConSub_MeasuringPoint measuringPoint=new ConSub_MeasuringPoint(); //创建屏幕显示告警观察者对象 Observer observer1=new Ob_ScreenDisplayAlarm(); //创建语音发声告警观察者对象 Observer observer2=new Ob_VoiceAudibleAlarm(); //创建电话拨号告警观察者对象 Observer observer3=new Ob_TelephoneDialingAlarm(); //创建电子邮件告警观察者对象 Observer observer4=new Ob_EmailAlarm(); //创建手机短信告警观察者对象 Observer observer5=new Ob_SMSAlarm(); //创建其他可利用的告警方式观察者对象 Observer observer6=new Ob_OtherAvailableAlarm(); //将观察者对象注册到主题对象上 measuringPoint.Attach(observer1); measuringPoint.Attach(observer2); measuringPoint.Attach(observer3); measuringPoint.Attach(observer4); measuringPoint.Attach(observer5); measuringPoint.Attach(observer6); //改变主题对象的状态 measuringPoint.DataOverThreshold("测点返回的采集数据超过阈值!!!"); } }
调试程序,运行结果如图所示:
五、实验结果和讨论
在编写完相应代码及测试程序后,调试程序,运行结果如图所示:
六、总结
(一)本次实验按时按量完成。通过实验基本掌握了行为型设计模式中的观察者模式。
(二)本次实验使用的是观察者模式。观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。 在观察者模式中,一个目标物件(被观察者)管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知,这通常通过呼叫各观察者所提供的方法来实现。
优点:
1. 目标和观察者对象之间的抽象耦合。一个目标所知道的仅仅是它有一系列观察者,每个都符合抽象的Observer类的简单接口。目标不知道任何一个观察者属于哪一个具体的类,它只知道它们都有一个共同的接口。这样目标和观察者之间的耦合是抽象的和最小的。
2. 支持广播通信。不像通常的请求,目标发送的通知不需指定它的接收者。通知被自动广播给所有已向该对象登记的有关对象。
缺点:
1. 意外的更新。因为一个观察者并不知道其它观察者的存在,它可能对改变目标的最终代价一无所知。虽然观察者模式可以随时使观察者知道所观察的对象发生了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎么发生变化的。
2. 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
3. 果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃。在使用观察者模式是要特别注意这一点。
4.如果对观察者的通知是通过另外的线程进行异步投递的话,系统必须保证投递是以自恰的方式进行的。
(三)使用观察者模式的心得体会:
从GOF给出的Observer模式的意图:“定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新”。中,我们可以得到两个信息,如下:
1.观察者(具体执行操作的对象,有多个)
2.被观察者(顾名思义是被观察的对象,如果该对象发生某些变化则通知观察者执行对应的操作。)
目标Suject提供依赖于它的观察者Observer的注册(Attach)和注销(Detach)操作,并且提供了使得依赖于它的所有观察者同步的操作(Notify)。
观察者则提供一个Update操作,需要注意的是这里的Observer的Update操作并不在Observer改变了Subject目标状态的时候就对自己进行更新,这个更新操作要延迟到Subject对象发出Notify通知所有Observer进行修改(调用Update)。
通过学习,我了解到Observer是影响极为深远的模式之一,也是在大型系统开发过程中要用到的模式之一。
七、附加要求
对于我们所介绍的常见的几种结构型的设计模式,举出这些模式的一些应用实例,并给出相关的UML类图说明。
此处以“学生逐级请假制度”举例如下:
大学学生有事情离开学校需要请假,请假1天,辅导员可以批准;请假2天,学院副书记可以批准;请假超过2天,则需要找学院院长批假!
此处则采用职责链模式来实现上述制度。
职责链模式是一种对象的行为模式。在职责链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象 决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织链和分配责任。
职责链模式的组成:
1、抽象处理者(Handler)角色:定义出一个处理请求的接口。如果需要,接口可以定义出一个方法,以设定和返回对后继者的引用。这个角色通常由一个抽象类或接口实现。
2、具体处理者(ConcreteHandler)角色:具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下家。由于具体处理者持有对下家的引用,因此,如果需要,具体处理者可以访问后继者。
3、客户端测试(ClientTest)角色:向职责链上的具体处理者(ConcreteHandler)对象提交请求。
UML类图设计如下所示:
(一) 代码设计如下所示:
1、处理请假请求的接口
package com.shen.cor; //处理请假请求的接口 public abstract class LeaveBaseHandler { //后继者 protected LeaveBaseHandler successor; //get 和set 方法 public LeaveBaseHandler getSuccessor() { return successor; } public void setSuccessor(LeaveBaseHandler successor) { this.successor = successor; } //处理请假请求的方法 public abstract void requestLeave(int days); }
2、对请假一天的处理
package com.shen.cor; //对请假一天的处理 public class OnedayLeaveConcreteHandler extends LeaveBaseHandler { @Override public void requestLeave(int days) { if (days<=1) { System.out.println(">>>辅导员可以批准请假1天"); } else { super.successor.requestLeave(days); } } }
3、对请假两天的处理
package com.shen.cor; //对请假两天的处理 public class TwodaysLeaveConcreteHandler extends LeaveBaseHandler { @Override public void requestLeave(int days) { if (days>1&&days<=2) { System.out.println(">>>学院副书记可以批准请假2天"); } else { super.successor.requestLeave(days); } } }
4、客户端测试类:
package com.shen.cor; //客户端测试类 public class ClientTest { private static void requestLeave() { LeaveBaseHandler oneDay=new OnedayLeaveConcreteHandler(); LeaveBaseHandler twoDays=new TwodaysLeaveConcreteHandler(); LeaveBaseHandler moreThanTwoDays=new MoreThanTwodaysConcreteHandler(); oneDay.setSuccessor(twoDays); twoDays.setSuccessor(moreThanTwoDays); oneDay.requestLeave(1); oneDay.requestLeave(2); oneDay.requestLeave(3); } public static void main(String[] args) { System.out.println("----------通过职责链模式实现逐级请假制度_105032013120----------"); requestLeave(); } }
(二) 测试结果如下所示: