📜  观察者模式 |第一套(介绍)

📅  最后修改于: 2021-09-10 02:54:02             🧑  作者: Mango

让我们首先考虑以下场景来理解观察者模式。

场景

假设我们正在构建一个板球应用程序,通知观众有关当前分数、运行率等信息。假设我们已经制作了两个显示元素 CurrentScoreDisplay 和 AverageScoreDisplay。 CricketData 拥有所有数据(跑步、保龄球等),每当数据更改时,显示元素都会收到新数据通知,并相应地显示最新数据。 o1

下面是这个设计的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 为他们提供数据。

类图: o2

图片来源:维基百科

  • 这里 Observer 和 Subject 是接口(可以是任何抽象超类型,不一定是Java接口)。
  • 所有需要数据的观察者都需要实现观察者接口。
  • 观察者接口中的 notify() 方法定义了主体向其提供数据时要采取的操作。
  • 主题维护一个观察者集合,它只是当前注册(订阅)观察者的列表。
  • registerObserver(observer) 和 unregisterObserver(observer) 分别是添加和删除观察者的方法。
  • 当数据更改并且需要为观察者提供新数据时,将调用 notifyObservers()。

好处:
在交互的对象之间提供松散耦合的设计。松散耦合的对象可以灵活应对不断变化的需求。这里松耦合意味着交互对象之间应该有较少的信息。

观察者模式提供了这种松散耦合:

  • 主体只知道观察者实现观察者接口。仅此而已。
  • 无需修改 Subject 即可添加或删除观察者。
  • 我们可以相互独立地重用主题和观察者类。

缺点:

  • 由于观察者的显式注册和取消注册,由失效侦听器问题引起的内存泄漏。

什么时候使用这种模式?
当多个对象依赖于一个对象的状态时,您应该考虑在您的应用程序中使用此模式,因为它为同一对象提供了一个整洁且经过良好测试的设计。

现实生活用途:

  • 它在 GUI 工具包和事件侦听器中大量使用。在Java,button(subject) 和 onClickListener(observer) 是用观察者模式建模的。
  • 社交媒体、RSS 提要、电子邮件订阅,您可以在其中选择关注或订阅并收到最新通知。
  • 如果有更新,Play 商店应用程序的所有用户都会收到通知。

观察者模式 |设置 2(实施)