让我们首先考虑以下场景来理解观察者模式。
场景:
假设我们正在构建一个板球应用程序,通知观众有关当前分数、运行率等信息。假设我们已经制作了两个显示元素 CurrentScoreDisplay 和 AverageScoreDisplay。 CricketData 拥有所有数据(跑步、保龄球等),每当数据更改时,显示元素都会收到新数据通知,并相应地显示最新数据。
下面是这个设计的Java实现。
// Java implementation of above design for Cricket App. The
// problems with this design are discussed below.
// A class that gets information from stadium and notifies
// display elements, CurrentScoreDisplay & AverageScoreDisplay
class CricketData
{
int runs, wickets;
float overs;
CurrentScoreDisplay currentScoreDisplay;
AverageScoreDisplay averageScoreDisplay;
// Constructor
public CricketData(CurrentScoreDisplay currentScoreDisplay,
AverageScoreDisplay averageScoreDisplay)
{
this.currentScoreDisplay = currentScoreDisplay;
this.averageScoreDisplay = averageScoreDisplay;
}
// Get latest runs from stadium
private int getLatestRuns()
{
// return 90 for simplicity
return 90;
}
// Get latest wickets from stadium
private int getLatestWickets()
{
// return 2 for simplicity
return 2;
}
// Get latest overs from stadium
private float getLatestOvers()
{
// return 10.2 for simplicity
return (float)10.2;
}
// This method is used update displays when data changes
public void dataChanged()
{
// get latest data
runs = getLatestRuns();
wickets = getLatestWickets();
overs = getLatestOvers();
currentScoreDisplay.update(runs,wickets,overs);
averageScoreDisplay.update(runs,wickets,overs);
}
}
// A class to display average score. Data of this class is
// updated by CricketData
class AverageScoreDisplay
{
private float runRate;
private int predictedScore;
public void update(int runs, int wickets, float overs)
{
this.runRate = (float)runs/overs;
this.predictedScore = (int) (this.runRate * 50);
display();
}
public void display()
{
System.out.println("\nAverage Score Display:\n" +
"Run Rate: " + runRate +
"\nPredictedScore: " + predictedScore);
}
}
// A class to display score. Data of this class is
// updated by CricketData
class CurrentScoreDisplay
{
private int runs, wickets;
private float overs;
public void update(int runs,int wickets,float overs)
{
this.runs = runs;
this.wickets = wickets;
this.overs = overs;
display();
}
public void display()
{
System.out.println("\nCurrent Score Display: \n" +
"Runs: " + runs +"\nWickets:"
+ wickets + "\nOvers: " + overs );
}
}
// Driver class
class Main
{
public static void main(String args[])
{
// Create objects for testing
AverageScoreDisplay averageScoreDisplay =
new AverageScoreDisplay();
CurrentScoreDisplay currentScoreDisplay =
new CurrentScoreDisplay();
// Pass the displays to Cricket data
CricketData cricketData = new CricketData(currentScoreDisplay,
averageScoreDisplay);
// In real app you would have some logic to call this
// function when data changes
cricketData.dataChanged();
}
}
输出:
Current Score Display:
Runs: 90
Wickets:2
Overs: 10.2
Average Score Display:
Run Rate: 8.823529
PredictedScore: 441
上述设计的问题:
- CricketData 持有对具体显示元素对象的引用,即使它只需要调用这些对象的更新方法。它可以访问超出其需要的太多附加信息。
- 这条语句“currentScoreDisplay.update(runs,wickets,overs);”违反了最重要的设计原则之一“编程接口,而不是实现”。因为我们使用具体对象而不是抽象接口来共享数据。
- CricketData 和显示元素紧密耦合。
- 如果将来出现另一个要求并且我们需要添加另一个显示元素,我们需要对代码的非变化部分(CricketData)进行更改。这绝对不是一个好的设计实践,应用程序可能无法处理更改并且不容易维护。
如何避免这些问题?
使用观察者模式
观察者模式
要理解观察者模式,首先需要理解主体和观察者对象。
主体和观察者之间的关系可以很容易地理解为杂志订阅的类比。
- 杂志出版商(主题)从事该业务并出版杂志(数据)。
- 如果您(数据的用户/观察者)对您订阅(注册)的杂志感兴趣,并且如果有新版本发布,它会发送给您。
- 如果您取消订阅(取消注册),您将停止获取新版本。
- 出版商不知道您是谁以及您如何使用该杂志,它只是将其交付给您,因为您是订阅者(松散耦合)。
定义:
观察者模式定义了对象之间的一对多依赖关系,以便一个对象改变状态,它的所有依赖项都会被自动通知和更新。
解释:
- 一对多依赖是在 Subject(One) 和 Observer(Many) 之间。
- 由于观察者本身无权访问数据,因此存在依赖性。他们依赖 Subject 为他们提供数据。
类图:
图片来源:维基百科
- 这里 Observer 和 Subject 是接口(可以是任何抽象超类型,不一定是Java接口)。
- 所有需要数据的观察者都需要实现观察者接口。
- 观察者接口中的 notify() 方法定义了主体向其提供数据时要采取的操作。
- 主题维护一个观察者集合,它只是当前注册(订阅)观察者的列表。
- registerObserver(observer) 和 unregisterObserver(observer) 分别是添加和删除观察者的方法。
- 当数据更改并且需要为观察者提供新数据时,将调用 notifyObservers()。
好处:
在交互的对象之间提供松散耦合的设计。松散耦合的对象可以灵活应对不断变化的需求。这里松耦合意味着交互对象之间应该有较少的信息。
观察者模式提供了这种松散耦合:
- 主体只知道观察者实现观察者接口。仅此而已。
- 无需修改 Subject 即可添加或删除观察者。
- 我们可以相互独立地重用主题和观察者类。
缺点:
- 由于观察者的显式注册和取消注册,由失效侦听器问题引起的内存泄漏。
什么时候使用这种模式?
当多个对象依赖于一个对象的状态时,您应该考虑在您的应用程序中使用此模式,因为它为同一对象提供了一个整洁且经过良好测试的设计。
现实生活用途:
- 它在 GUI 工具包和事件侦听器中大量使用。在Java,button(subject) 和 onClickListener(observer) 是用观察者模式建模的。
- 社交媒体、RSS 提要、电子邮件订阅,您可以在其中选择关注或订阅并收到最新通知。
- 如果有更新,Play 商店应用程序的所有用户都会收到通知。
观察者模式 |设置 2(实施)