描述:
在设计一组依靠的对象与它们所依靠的对象之间一致(同步)的交流模型时,观察者模式(Observer Pattern)很有用。它可以使依靠对象的状态与它们所依靠的对象的状态保持同步。这组依靠的对象指的是观察者(Observer),它们所依靠的对象称为主题(Subject)。为了实现观察者(Observer)的状态与主题(Subject)保持同步,观察者模式(Observer Pattern)
推荐采用发布者--订阅者(publisher--subscriber)模型,以使这组观察者(Observer)和主题(Subject)对象之间有清楚的界限。
典型的观察者(Observer)是一个依靠于或者关注于主题对象的状态的对象。一个主题可以有一个或者多个观察者。这些观察者在主体的状态发生变化时,需要得到通知。
由于给定主体的观察者链表需要动态的变化,因此一个主题不能维护一个静态的观察者链表。因此关注于主题状态的任何对象都需要明确地注册自己为主体的一个观察者。主题状态发生的变化,都需要通知所有的以注册的观察者。从主题接到通知以后,每一个观察者查询主题,使自己的状态与主题的同步。因此一个主题扮演着发布者的角色,发布信息到所有的以订阅的观察者。
换句话说,主题和它的观察者之间包含了一对多的关系。当主题的实例的状态发生变化时,所有的依靠于它的观察者都会得到通知并更新自己。每一个观察者对象需要向主题注册,当主题的状态发生变化的时候得到通知。一个观察者可以注册或者订阅多个主题。当观察者不希望再得到通知时,它可以向主题进行注销。
为了实现这种机制:
(1)主题需要为注册和注销通知提供一个接口。
(2)下面的两点也需要满足:
A、拉模型(In the pull model)--主题需要提供一个接口,可以使观察者查询主题获得需要的状态信息来更新自己的状态。
B、推模型(In the push model)--主题发送观察者可能关注的状态信息。
(3)观察者需要提供一个可以从主题接受通知的接口。
类图(图33.1)描述了为满足于以上需求,不同类的结构和它们之间的关联关系。
Figure 33.1: Generic Class Association When the Observer Pattern Is Applied
从这个类图可以看到:
(1)所有的主题需要提供一个类似于Observable接口的实现。
(2)所有的观察者需要提供一个类似于Observer接口的实现。
在应用观察者模式时,有几种变体。这就会产生不同类型的主题--观察者模式,例如,观察者仅关注主体特定类型的变化等。
增加新的观察者:
应用观察者模式以后,在不影响主题类的情况下,可以动态的加入不同的观察者。同样,主题的状态变化逻辑改变时,观察者也不会受到影响。
例子:
为了治理一个卖厂多个分类产品,让我们建立一个销售报表系统。这个系统有以下特征:
(1)用户可以选择一个他们感爱好的分类
(2)在选择了一个分类以后,需要显示下面的两种类型的报表。
A、月度报表(Monthly report)--所选分类当月的所有交易清单。
B、年度累积额(YTD sales chart)--以月为单位显示选择分类的年度累积额图。
(3)当一个不同的分类被选择时,两种报表的数据会被刷新,显示当前所选分类的报表。
为了实现以上期望的功能,我们很轻易的看到两个报表对象依靠于持有用户选择分类的对象。应用观察者模式于此场景,我们可以设计一个介于持有用户选择分类的对象和两个报表对象之间一个一致(同步)的交流模型。
让我们定义三个类,它们的功能如表33.1所描述:
Table 33.1: Subject-Observer Classes
public interface Observable {
public void notifyObservers();
public void register(Observer obs);
public void unRegister(Observer obs);
}
ReportManager类(Listing33.1)提供了声明在Observable接口中方法的实现。两个依靠于ReportManager的报表对象使用这些方法注册它们自己为观察者。ReportManager把这些注册的观察者保存到observersList矢量(vector)中。当前选择的分类构成了ReportManager对象的状态,它以实例变量的形式保存在变量department中。当为department设置一个新的值时(也就是ReportManager对象的状态改变),notifyObservers方法被调用。作为notifyObservers方法的一部分,ReportManager调用注册为观察者的refreshData(Observable)方法。
Listing 33.1: ReportManager Class
public class ReportManager extends JFrame
implements Observable {
…
…
PRivate Vector observersList;
private String department;
public ReportManager() throws Exception {
…
…
observersList = new Vector();
…
…
}
public void register(Observer obs) {
//Add to the list of Observers
observersList.addElement(obs);
}
public void unRegister(Observer obs) {
//remove from the list of Observers
}
public void notifyObservers() {
//Send notify to all Observers
for (int i = 0; i < observersList.size(); i++) {
Observer observer =
(Observer) observersList.elementAt(i);
observer.refreshData(this);
}
}
public String getDepartment() {
return department;
}
public void setDepartment(String dept) {
department = dept;
}
class ButtonHandler implements ActionListener {
ReportManager subject;
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals(ReportManager.EXIT)) {
System.exit(1);
}
if (e.getActionCommand().equals(ReportManager.SET_OK)) {
String dept = (String)
cmbDepartmentList.getSelectedItem();
//change in state
subject.setDepartment(dept);
subject.notifyObservers();
}
}
public ButtonHandler() {
}
public ButtonHandler(ReportManager manager) {
subject = manager;
}
}
}//end of class
除了提供Observable接口方法的实现,ReportManager还显示了必要的用户接口,答应用户选择一个特定的、关注的分类。
让我们定义接口Observer的两个实现:MonthlyReport和YTDChart类
public interface Observer {
public void refreshData(Observable subject);
}
Figure 33.2: Observer Class Hierarchy
Listing 33.2: MonthlyReport Class as an Observer
public class MonthlyReport extends JFrame implements Observer {
…
…
private ReportManager objReportManager;
public MonthlyReport(ReportManager inp_objReportManager)
throws Exception {
super("Observer Pattern -- Example");
objReportManager = inp_objReportManager;
//Create controls
…
…
//Create Labels
…
…
objReportManager.register(this);
}
public void refreshData(Observable subject) {
if (subject == objReportManager) {
//get subject's state
String department = objReportManager.getDepartment();
lblTransactions.setText(
"Current Month Transactions - " +
department);
Vector trnList =
getCurrentMonthTransactions(department);
String content = "";
for (int i = 0; i < trnList.size(); i++) {
content = content +
trnList.elementAt(i).toString() + "\n";
}
taTransactions.setText(content);
}
}
private Vector getCurrentMonthTransactions(String department
) {
Vector v = new Vector();
FileUtil futil = new FileUtil();
Vector allRows = futil.fileToVector("Transactions.date");
//current month
Calendar cal = Calendar.getInstance();
cal.setTime(new Date());
int month = cal.get(Calendar.MONTH) + 1;
String searchStr = department + "," + month + ",";
int j = 1;
for (int i = 0; i < allRows.size(); i++) {
String str = (String) allRows.elementAt(i);
if (str.indexOf(searchStr) ?1) {
StringTokenizer st =
new StringTokenizer(str, ",");
st.nextToken();//bypass the department
str = " " + j + ". " + st.nextToken() + "/" +
st.nextToken() + "~~~" +
st.nextToken() + "Items" + "~~~" +
st.nextToken() + " Dollars";
j++;
v.addElement(str);
}
}
return v;
}
}//end of class
ReportManager利用这个接口通知它的所有观察者。
主题--观察者的关联(Subject--Observer Association)
通常,一个客户首先需要创建一个主题(ReportManager)实例,当一个观察者(例如:MonthlyReport,YTDChart)对象被创建。客户把主题ReportManager实例的引用传递给观察者的构造函数,观察者将自身注册到当前主题实例上。
//Client Code
public class SupervisorView {
…
…
public static void main(String[] args) throws Exception {
//Create the Subject
ReportManager objSubject = new ReportManager();
//Create Observers
new MonthlyReport(objSubject);
new YTDChart(objSubject);
}
}//end of class
类之间的关联描述如下:
Figure 33.3: Example application--Class Association
逻辑流程:
(1)使用ReportManager用户接口,当用户选择一个特定的分类并且点击OK按钮时,ReportManager的内部状态被被改变(例如,ReportManager实例变量department的值发生改变)。
(2)新的状态一旦被设置,ReportManager调用两个注册的观察者MonthlyReport和YTDChart的refreshData(Observable)方法。
(3)作为refreshData方法的一部分,两个report对象需要:
A、检查以确保调用refreshData方法的主题和观察者这册的主题是同一个主题。这就避免了观察者响应不必要的调用。
B、使用getDepartment方法查询ReportManager的当前状态。
C、从数据文件中提取响应的数据显示。
Figure 33.4: MonthlyReport View
当ReportManager的状态变化逻辑实现需要改变时,任何观察者不受影响。同样,当一个新的观察者被加入时,ReportManager类不需要任何变化。