【愚公系列】2023年10月 二十三种设计模式(十九)-观察者模式(Observer Pattern)

举报
愚公搬代码 发表于 2021/12/05 00:29:49 2021/12/05
【摘要】 设计模式(Design Pattern)是软件开发领域的宝贵经验,是多人反复借鉴和广泛应用的代码设计指导。它们是一系列经过分类和归纳的代码组织方法,旨在实现可重用性、可维护性和可理解性。使用设计模式,我们能够编写高质量的代码,使其更易于他人理解,并提供了代码可靠性的保证。

🏆 作者简介,愚公搬代码
🏆《头衔》:华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,阿里云专家博主,腾讯云优秀博主,掘金优秀博主,51CTO博客专家等。
🏆《近期荣誉》:2022年CSDN博客之星TOP2,2022年华为云十佳博主等。
🏆《博客内容》:.NET、Java、Python、Go、Node、前端、IOS、Android、鸿蒙、Linux、物联网、网络安全、大数据、人工智能、U3D游戏、小程序等相关领域知识。
🏆🎉欢迎 👍点赞✍评论⭐收藏

🚀前言

设计模式(Design Pattern)是软件开发领域的宝贵经验,是多人反复借鉴和广泛应用的代码设计指导。它们是一系列经过分类和归纳的代码组织方法,旨在实现可重用性、可维护性和可理解性。使用设计模式,我们能够编写高质量的代码,使其更易于他人理解,并提供了代码可靠性的保证。

毫无疑问,设计模式对个人、团队和整个系统都带来了显著的益处。它们将代码开发提升到工程化水平,为软件工程构建提供了坚实的基础,就如同大厦的一块块精巧的砖石一样。在项目中明智地应用设计模式可以完美地解决各种复杂问题。每种设计模式都有相应的原理和最佳实践,它们描述了我们日常开发中不断遇到的问题,以及这些问题的核心解决方法。正是因为这种实用性和通用性,设计模式才能在软件开发中广泛地得以应用。设计模式是构建稳健、可扩展和可维护软件的关键工具,为开发者们提供了解决问题的智慧和指导。

🚀一、观察者模式(Observer Pattern)

观察者模式,又称为模型-视图模式、发布-订阅模式、源-监听器模式或从属者模式,属于行为型设计模式。它巧妙地将观察者和被观察的对象分离,通过定义接口方法、抽象方法、委托或事件,实现了当目标物件状态改变时主动向观察者发出通知。这一模式有助于在应用程序中明确模块之间的边界,提高了可维护性和重用性。

观察者模式的核心思想是对象之间的松耦合,其中包括以下关键角色:

  • 被观察者(Subject):也称为目标(Observable)或主题(Publisher),它维护了一个观察者列表,并提供方法来注册、移除和通知观察者。当其状态改变时,会通知所有注册的观察者。

  • 观察者(Observer):观察者是依赖于被观察者的对象,它实现了特定的接口或抽象方法,以便在被观察者状态改变时接收通知并作出相应的响应。

观察者模式的优点包括:

  1. 解耦合:被观察者和观察者之间的解耦合,使得它们可以独立变化,不影响彼此。

  2. 可扩展性:可以轻松添加新的观察者或被观察者,扩展系统功能。

  3. 通知机制:观察者模式提供了一种通知机制,使观察者能够及时获得信息并采取行动。

  4. 模块化:将系统拆分为多个模块,每个模块专注于特定的功能,提高了可维护性和重用性。

  5. 实时性:观察者能够实时响应状态变化,适用于需要实时更新的场景。

为了优化和扩展观察者模式,可以考虑以下方法:

  • 使用现代语言特性:使用现代编程语言的特性,如C#的事件、Java的观察者模式,以简化观察者模式的实现。

  • 引入中介者:在多个观察者之间引入中介者,可以降低复杂性,提高可维护性。

  • 异步通知:考虑使用异步通知机制,以避免观察者对被观察者的操作产生阻塞。

  • 异常处理:在通知观察者时,考虑如何处理可能出现的异常情况,以增强健壮性。

观察者模式是一种强大的设计模式,可以用于构建松耦合、可维护和可扩展的应用程序。通过合理地设计和优化,可以充分发挥其优势。

🚀二、使用步骤

🔎1.角色

🦋1.1 抽象主题(Subject)

观察者模式(Observer Pattern)中的抽象主题(Subject)是设计模式的核心组成部分,具有以下概念和作用:

  1. 概念

    • 抽象主题(Subject):也称为被观察者(Observable)或发布者(Publisher),是一个抽象类或接口,定义了被观察对象应该具备的基本行为和方法。这些方法通常包括注册观察者、移除观察者以及通知观察者的操作。
  2. 作用

    • 维护观察者列表:抽象主题维护一个观察者(Observer)列表,用于存储所有注册的观察者对象。这个列表允许动态添加和移除观察者,以便在状态变化时通知它们。

    • 注册和移除观察者:抽象主题提供了方法来注册(添加)和移除观察者。当对象希望开始观察某个主题时,它会调用注册方法,将自己添加到主题的观察者列表中。相反,当对象不再希望观察主题时,它可以调用移除方法,将自己从观察者列表中移除。

    • 通知观察者:抽象主题负责在自身状态发生变化时通知所有注册的观察者。通常,它会调用观察者的特定方法(例如,update),将状态改变的信息传递给观察者,以便观察者能够根据新状态采取适当的行动。

    • 状态管理:抽象主题通常包含一个用于管理状态的属性或方法。当状态发生变化时,它会更新自身的状态,并在通知观察者之前保存新状态。

    • 实际主题的基类:在实际应用中,通常会创建一个具体的主题类,它继承自抽象主题,实现了具体的业务逻辑。这个具体主题类负责维护状态并在状态变化时通知观察者。

抽象主题的作用在于将观察者模式的核心行为和接口抽象化,使得具体的被观察者类可以通过继承或实现抽象主题来轻松地实现观察者模式。同时,抽象主题也确保了被观察者和观察者之间的松耦合,因为观察者只需要依赖于抽象主题的接口,而不需要直接与具体的被观察者类交互。这有助于提高代码的可维护性、可扩展性和重用性。通过抽象主题,我们可以实现多种不同的具体主题类,每个类都可以有自己的状态和观察者列表,而不影响整体的观察者模式的

🦋1.2 具体主题(Concrete Subject)

在观察者模式(Observer Pattern)中,具体主题(Concrete Subject)是被观察者的具体实现,具有以下概念和作用:

  1. 概念

    • 具体主题(Concrete Subject):具体主题是抽象主题(Subject)接口的实现类,代表了被观察者对象的具体实例。它包含了一些特定的状态和数据,并负责维护观察者对象的注册和通知观察者的操作。
  2. 作用

    • 维护观察者列表:具体主题负责维护一个观察者列表,该列表存储了所有注册到该主题上的具体观察者对象。这个列表通常以数据结构(如列表、集合或数组)的形式存储。

    • 状态管理:具体主题包含了一些状态或数据,它们表示了被观察者的当前状态。当这些状态发生变化时,具体主题会负责通知注册的观察者对象。

    • 注册观察者:具体主题提供注册观察者的方法,允许具体观察者将自己注册到被观察者上。一旦注册,观察者将开始接收被观察者的状态变化通知。

    • 移除观察者:具体主题也提供了移除观察者的方法,允许观察者取消对被观察者的观察。这样做可以确保不再通知不再感兴趣的观察者。

    • 状态变化通知:当具体主题的状态发生变化时,它会遍历观察者列表,并调用每个观察者的更新方法,以通知它们状态的变化。这样,观察者可以根据新的状态执行适当的操作。

    • 实现业务逻辑:具体主题还包括了一些业务逻辑,用于确定何时以何种方式通知观察者。这些逻辑通常涉及到状态的变化条件和触发通知的时机。

具体主题是观察者模式中的核心部分,它通过维护观察者列表和状态数据,实现了被观察者和观察者之间的解耦。具体主题的存在使得多个观察者可以同时监听和响应被观察者的状态变化,从而实现了一对多的依赖关系,同时也提高了系统的可扩展性和灵活性。

🦋1.3 抽象观察者(Observer)

观察者模式(Observer Pattern)中的抽象观察者(Observer)是这一模式的关键组成部分,具有以下概念和作用:

  1. 概念

    • 抽象观察者(Observer):抽象观察者是一个接口或抽象类,定义了观察者对象应该具备的基本行为和方法。通常,这个接口包含了一个更新(update)方法,用于在被观察者状态发生变化时接收通知。
  2. 作用

    • 定义观察者接口:抽象观察者定义了观察者对象应该实现的接口,这包括了更新方法的签名。通过这个接口,确保了具体观察者都有相同的方式来接收和响应状态变化的通知。

    • 实现多态性:抽象观察者允许不同类型的具体观察者对象注册到同一个被观察者对象上。这意味着被观察者不需要知道具体观察者的类型,从而实现了松耦合。

    • 接收通知并响应:具体观察者类会实现抽象观察者接口,其中的更新方法用于接收来自被观察者的通知。当被观察者状态发生变化时,它会调用每个注册的观察者的更新方法,从而观察者可以根据新状态采取适当的行动。

    • 支持多个观察者:抽象观察者的存在使得被观察者可以支持多个观察者,这些观察者可以同时观察被观察者的状态变化,而不会相互影响。

    • 实现自定义观察者:通过实现抽象观察者接口,可以创建自定义的观察者类,以便在不同的情景下实现特定的观察逻辑。

抽象观察者的作用在于定义了观察者对象应该具备的通用接口,使得具体观察者可以根据需要进行定制化实现,同时确保了被观察者和观察者之间的松耦合,从而提高了系统的可维护性和扩展性。这样,可以轻松地在系统中添加、移除或替换观察者,而不需要修改被观察者的代码。这种模式通常用于构建事件处理系统、消息通知系统以及各种需要实现发布-订阅

🦋1.4 具体观察者(Concrete Observer)

在观察者模式(Observer Pattern)中,具体观察者(Concrete Observer)是实现了抽象观察者接口的具体类,具有以下概念和作用:

  1. 概念

    • 具体观察者(Concrete Observer):具体观察者是抽象观察者的实现类,它实现了抽象观察者定义的接口,包括更新方法。每个具体观察者都代表了一个具体的观察者对象。
  2. 作用

    • 实现观察者接口:具体观察者实现了抽象观察者定义的接口,其中包括了更新方法。这个方法会在被观察者的状态发生变化时被调用,以便观察者能够接收通知并采取相应的行动。

    • 定义观察者的具体行为:每个具体观察者可以根据自身的需求来实现更新方法。这意味着不同的具体观察者可以有不同的响应方式,从而实现了多样化的观察行为。

    • 保持状态:具体观察者可以在更新方法中获取被观察者传递的状态信息,从而能够根据新状态执行相应的逻辑。

    • 实现业务逻辑:具体观察者可以利用被观察者的状态信息来实现特定的业务逻辑。例如,在一个新闻订阅应用中,具体观察者可以根据新闻的类型和内容来选择是否显示通知。

    • 多个具体观察者的存在:一个被观察者可以同时拥有多个具体观察者,每个具体观察者都可以独立地处理通知,从而实现多个不同观察者同时监听同一个被观察者的状态变化。

具体观察者的存在使得我们可以根据具体业务需求创建不同的观察者类,每个类都可以有自己的响应方式和逻辑。这种灵活性使得观察者模式在实际应用中非常有用,特别是在需要实现一对多的依赖关系时。

🔎2.示例

在这里插入图片描述

命名空间ObserverPattern中包含抽象出版社基类Publisher(主题)、中国机械工业出版社类Machine、中国农业出版社类Agriculture、读者接口IReader(观察者)、具体观察者Iori类和Jay类、图书类Book。另外为了代码更整洁,引入Extentions扩展类,方便图书和读者信息的处理。这个示例展示读者如何观察出版社发布图书的状态,并在出版社发布图书时,得到通知。

public class Book {

    public string Name { get; set; }

    public Book(string name) {
        Name = name;
    }

}

简单的图书类,仅包含一个构造函数和图书的名字属性。

public interface IReader {

    void Receive(Publisher publisher, Book book);

}

读者接口,定义公开的Receive契约,并且得到出版社和图书信息。

public class Iori : IReader {
 
    public void Receive(Publisher publisher, Book book) {
        Console.WriteLine(
            $"{this.ReaderName()} received {book.BookName()} from {publisher.Name}.");
    }
 
}
public class Jay : IReader {
 
    public void Receive(Publisher publisher, Book book) {
        Console.WriteLine(
            $"{this.ReaderName()} received {book.BookName()} from {publisher.Name}.");
    }
 
}

具体读者类,Iori和Jay,一个是我的英文名,另一个则是我的偶像。

public abstract class Publisher {
 
    private List<IReader> _readers = new List<IReader>();
 
    public string Name { get; set; }
    private const string LINE_BREAK =
        "----------------------------------------" +
        "----------------------------------------";
    //文章排版需要,故折成2行
 
    public void AttachReader(IReader reader) {
        if (reader == null) throw new ArgumentNullException();
        _readers.Add(reader);
    }
 
    public bool DetachReader(IReader reader) {
        if (reader == null) throw new ArgumentNullException();
        return _readers.Remove(reader);
    }
 
    protected virtual void OnPublish(Book book, DateTime publishTime) {
        Console.WriteLine(
            $"{Name} published {book.BookName()} at {publishTime.ToString("yyyy-MM-dd")}.");
        Console.WriteLine(LINE_BREAK);
    }
 
    public void Publish(Book book, DateTime publishTime) {
        OnPublish(book, publishTime);
        foreach (var reader in _readers) {
            if (reader != null) {
                reader.Receive(this, book);
            }
        }
        Console.WriteLine(LINE_BREAK);
    }
 
}

抽象出版社类Publisher,即被观察者,这是整个观察者模式的核心基类。首先在内部维持对IReader列表的引用,并且可以对观察者进行增加(AttachReader)或删除(DetachReader)操作。而发布方法Publish则在出版社发布新图书时,通知所有观察者。

public class Machine : Publisher {
 
    public Machine(string name) {
        Name = name;
    }
 
    protected override void OnPublish(Book book, DateTime publishTime) {
        Console.WriteLine(
            $"{Name} published {book.BookName()} at {publishTime.ToString("yyyy-MM-dd")}." +
            $"->Machine.OnPublish");
        Console.WriteLine(LINE_BREAK);
    }
 
}
public class Agriculture : Publisher {

    public Agriculture(string name) {
        Name = name;
    }

}

具体出版社类Machine和Agriculture,代表中国机械出版社和中国农业出版社。

public static class Extentions {

    public static string ReaderName(this IReader reader) {
        return reader.ToString().Replace(nameof(ObserverPattern) + ".", "");
    }

    public static string BookName(this Book book) {
        return "[" + book.Name + "]";
    }

}

公开的静态的扩展方法类,其中ReaderName扩展处理读者名称前的命名空间,使用nameof关键字是为了支持重构。BookName扩展则用来为书名加上中括号。

public class Program {

    public static void Main(string[] args) {
        Publisher publisher = new Machine("China Machine Press");

        var iori = new Iori();
        var jay = new Jay();

        publisher.AttachReader(iori);
        publisher.AttachReader(jay);

        publisher.Publish(new Book("How the Steel Was Tempered"), DateTime.UtcNow);

        publisher.DetachReader(jay);

        publisher.Publish(new Book("Jane Eyre"), DateTime.UtcNow);

        publisher = new Agriculture("China Agriculture Press");

        publisher.AttachReader(iori);
        publisher.AttachReader(jay);

        publisher.Publish(new Book("Romance of the Three Kingdoms"), DateTime.UtcNow);

        Console.ReadKey();
    }

}

这个是调用方的代码示例,首先创建机械工业出版社实例,再创建2个具体读者实例并订阅,最后出版社发布《钢铁是怎么炼成的》图书,2个读者都能收到发布通知。接下来演示了取消订阅和中国农业出版社的发布情况,请各位看官自行分析。以下是这个案例的输出结果:

China Machine Press published [How the Steel Was Tempered] at 2018-07-19.->Machine.OnPublish
--------------------------------------------------------------------------------
Iori received [How the Steel Was Tempered] from China Machine Press.
Jay received [How the Steel Was Tempered] from China Machine Press.
--------------------------------------------------------------------------------
China Machine Press published [Jane Eyre] at 2018-07-19.->Machine.OnPublish
--------------------------------------------------------------------------------
Iori received [Jane Eyre] from China Machine Press.
--------------------------------------------------------------------------------
China Agriculture Press published [Romance of the Three Kingdoms] at 2018-07-19.
--------------------------------------------------------------------------------
Iori received [Romance of the Three Kingdoms] from China Agriculture Press.
Jay received [Romance of the Three Kingdoms] from China Agriculture Press.
--------------------------------------------------------------------------------

🚀总结

🔎1.优点

观察者模式(Observer Pattern)具有许多优点,使其成为软件开发中常用的设计模式之一。以下是观察者模式的一些主要优点:

  1. 松耦合(Loose Coupling):观察者模式实现了被观察者和观察者之间的松耦合关系。被观察者不需要知道观察者的具体类别,只需知道它们实现了相同的观察者接口。这使得系统的组件更容易理解、扩展和维护。

  2. 一对多关系:观察者模式支持一对多的关系,一个被观察者可以同时通知多个观察者对象。这使得可以轻松地添加或删除观察者,而不影响被观察者或其他观察者的代码。

  3. 分离关注点:观察者模式将关注点分离开来,被观察者负责维护状态,而观察者负责响应状态变化。这使得系统更易于扩展,因为可以独立地修改被观察者和观察者的逻辑。

  4. 可重用性:观察者模式中的被观察者和观察者是独立的模块,可以在不同的上下文中重用。这有助于提高代码的可重用性和模块化。

  5. 实时通知:观察者模式允许观察者实时地获得被观察者的状态变化通知,这对于需要实时响应事件或状态变化的应用程序非常有用,如消息传递系统或事件处理系统。

  6. 灵活性:观察者模式可以轻松地支持多种观察者和被观察者的组合,因此可以适用于各种不同的应用场景。

  7. 可维护性:由于关注点分离和松耦合的特性,观察者模式使系统更易于维护。当需要修改或扩展系统时,只需关注相关的被观察者或观察者,而不必修改整个系统。

观察者模式提供了一种优雅的方式来实现对象之间的通信和协作,使系统更灵活、可维护和可扩展。它适用于许多不同的应用场景,特别是需要实现一对多关系的情况。

🔎2.缺点

观察者模式(Observer Pattern)虽然具有许多优点,但也存在一些缺点,需要考虑在使用时加以注意:

  1. 内存泄漏风险:如果观察者没有被正确地移除,或者观察者不被管理,可能导致内存泄漏。观察者模式中,被观察者会保持对所有观察者的引用,如果观察者没有正确地被移除,就会导致观察者无法被垃圾回收,造成内存泄漏。

  2. 顺序依赖:观察者模式中,观察者的执行顺序是不确定的,这可能导致观察者依赖于特定的通知顺序,从而影响系统的正确性。

  3. 性能问题:观察者模式可能导致性能问题,特别是在通知多个观察者时,可能会消耗较多的时间。如果观察者的响应时间较长或触发通知频繁,可能会影响整体系统的性能。

  4. 可能引发无限循环:观察者模式中,观察者可能在接收到通知后再次改变被观察者的状态,从而引发另一次通知。这可能导致无限循环,影响系统的稳定性和可靠性。

  5. 通知方式限制:标准的观察者模式中,通知是广播式的,即通知所有观察者。如果只想通知特定类型的观察者或特定条件下的观察者,可能需要额外的逻辑来处理。

  6. 复杂性增加:观察者模式引入了更多的类和接口,可能增加了系统的复杂性,特别是在处理多个观察者和被观察者时,可能会增加代码量和维护难度。

  7. 可能导致竞态条件:在多线程环境下,观察者模式可能导致竞态条件(race condition)和并发问题,需要额外的同步机制来保证线程安全。

使用观察者模式时需要谨慎考虑上述可能出现的缺点,并根据具体应用场景进行权衡和处理,以确保系统的稳定性、性能和可维护性。

🔎3.使用场景

观察者模式(Observer Pattern)适用于许多不同的场景,特别是在需要实现一对多的依赖关系以及对象状态变化通知的情况下。以下是观察者模式常见的使用场景:

  1. 事件处理和通知系统:当需要实现事件处理系统或通知机制时,观察者模式是一个理想的选择。例如,图形用户界面(GUI)库中的按钮点击事件、消息传递系统中的消息通知等都可以使用观察者模式来实现。

  2. 发布-订阅模型:观察者模式是发布-订阅(Pub-Sub)模型的核心,用于实现消息发布和订阅机制。发布者将消息发送给多个订阅者,订阅者可以根据其兴趣订阅特定类型的消息。

  3. UI开发:在用户界面开发中,观察者模式常用于处理用户界面组件的交互。例如,当一个文本框的内容发生变化时,可以通知所有注册的文本框监听器进行更新。

  4. 库和框架:许多库和框架使用观察者模式来允许开发者扩展其功能。例如,Java中的JavaBeans框架就使用了观察者模式,允许属性变化时通知监听器。

  5. 实时数据更新:当需要实时更新数据或状态时,观察者模式非常有用。例如,股票市场应用程序可以使用观察者模式来通知用户股票价格的变化。

  6. 游戏开发:在游戏开发中,观察者模式可以用于处理游戏中的事件和状态变化。例如,角色状态的变化可以通知其他角色或系统进行响应。

  7. 分布式系统:在分布式系统中,观察者模式可用于实现分布式事件处理和通信。一个节点的状态变化可以通知其他节点进行相应操作。

  8. 日志记录:在日志记录系统中,观察者模式可以用于将日志消息发送给多个日志处理器,例如文件日志、数据库日志、控制台输出等。

观察者模式适用于任何需要对象之间松耦合通信和状态变化通知的情况。它提供了一种灵活的方式来实现对象之间的交互,同时保持系统的可维护性和扩展性。


🚀感谢:给读者的一封信

亲爱的读者,

我在这篇文章中投入了大量的心血和时间,希望为您提供有价值的内容。这篇文章包含了深入的研究和个人经验,我相信这些信息对您非常有帮助。

如果您觉得这篇文章对您有所帮助,我诚恳地请求您考虑赞赏1元钱的支持。这个金额不会对您的财务状况造成负担,但它会对我继续创作高质量的内容产生积极的影响。

我之所以写这篇文章,是因为我热爱分享有用的知识和见解。您的支持将帮助我继续这个使命,也鼓励我花更多的时间和精力创作更多有价值的内容。

如果您愿意支持我的创作,请扫描下面二维码,您的支持将不胜感激。同时,如果您有任何反馈或建议,也欢迎与我分享。

在这里插入图片描述

再次感谢您的阅读和支持!

最诚挚的问候, “愚公搬代码”

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。