软件设计模式之观察者模式

软件设计模式之观察者模式

观察者模式定义了对象之间的一对多依赖关系,以便一个对象改变状态,其所有依赖关系都会得到通知并自动更新。

技术开发 编程 技术框架 技术发展

 

软件设计模式之观察者模式

观察者模式定义了对象之间的一对多依赖关系,以便一个对象改变状态,其所有依赖关系都会得到通知并自动更新。

场景:

假设我们正在构建一个板球应用程序,该程序可以向观众通知诸如当前得分,跑步率等信息。假设我们已经创建了两个显示元素CurrentScoreDisplay和AverageScoreDisplay。CricketData拥有所有数据(行程,碗等),每当数据更改时,显示元素就会收到新数据的通知,并相应地显示最新数据。

ObserverPatternSet-1.png

// 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(" Average Score Display: " +
                           "Run Rate: " + runRate +
                           " PredictedScore: " + 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(" Current Score Display: " +
                           "Runs: " + runs +" Wickets:"
                           + wickets + " Overs: " + 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();
    }
}

Output:

Current Score Display: 
Runs: 90
Wickets:2
Overs: 10.2

Average Score Display:
Run Rate: 8.823529
PredictedScore: 441

以上设计存在的问题:

  • CricketData保留对具体显示元素对象的引用,即使它仅需要调用这些对象的update方法。它有权访问过多的其他信息。

  • 这条语句“ currentScoreDisplay.update(runs,wickets,overs);” 违反了最重要的设计原则之一“程序是接口,而不是实现”。因为我们正在使用具体对象共享数据而不是抽象接口。

  • CricketData和显示元素紧密耦合。

  • 如果将来有另一个要求,并且需要添加另一个显示元素,则需要对代码的不变部分(CricketData)进行更改。这绝对不是一个好的设计实践,并且应用程序可能无法处理更改并且不易于维护。

如何避免这些问题?要了解观察者模式,首先需要了解主题和观察者对象。

  • 主题和观察者之间的关系可以很容易地理解为类似于杂志订阅。

  • 杂志出版商(主题)在业务中,并出版杂志(数据)。

  • 如果您(数据用户/观察者)对您订阅(注册)的杂志感兴趣,并且如果发布了新版本,它将发送给您。

  • 如果您取消订阅(取消注册),则停止获取新版本。

  • 发行人不知道您是谁,以及如何使用该杂志,它只是将其提供给您,因为您是订户(松耦合)。

定义:

观察者模式定义了对象之间的一对多依赖关系,以便一个对象改变状态,其所有依赖关系都会得到通知并自动更新。

说明:

主题(一个)和观察者(许多)之间存在一对多的依赖关系。

存在依赖性,因为观察者本身无权访问数据。他们依赖于主题提供数据。

类图:

o2.png

  • 这里的Observer和Subject是接口(可以是任何抽象超类型,不一定是java接口)。

  • 所有需要数据的观察者都需要实现观察者接口。

  • 观察者接口中的notify()方法定义主题提供数据时要采取的措施。

  • 该主题维护一个observerCollection,它只是当前已注册(订阅)的观察者的列表。

  • registerObserver(observer)和unregisterObserver(observer)是分别添加和删除观察者的方法。

  • 当数据更改并且需要向观察者提供新数据时,将调用notifyObservers()。

优点:

在交互的对象之间提供松散耦合的设计。松散耦合的对象可以随需求的变化而灵活变化。在这里,松散耦合意味着相互作用的对象彼此之间的信息较少。

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

主体只知道观察者实现了Observer接口,仅此而已。

无需修改Subject即可添加或删除观察者。

我们可以彼此独立地重用主题和观察者类。

缺点:

由于观察者的显式注册和取消注册,导致侦听器问题导致的内存泄漏。

何时使用此模式?

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

现实生活中的用途:

它在GUI工具包和事件侦听器中大量使用。在Java中,button(主题)和onClickListener(observer)使用观察者模式建模。

社交媒体,RSS提要,电子邮件订阅,您可以选择关注或订阅,并且会收到最新通知。

Play商店中某个应用的所有用户都会收到更新通知。

技术开发 编程 技术框架 技术发展