Observer Pattern

The observer pattern is a behavioral pattern, sometimes referred to as model-view, publish-subscribe, source-listener, or Dependents.

The observer pattern perfectly separates the observer from the observed object and actively notifies the observer (interface methods, abstract methods, delegates, events) when the state of the target object changes. The Observer pattern sets clear boundaries between modules, improving application maintainability and reuse.

role

1. Abstract Subject

The topic needs to maintain references to all observers in order to invoke the observer interface when the state changes. Each theme can have any number of observers, and can add and remove observer objects;

2. Concrete Subject

Save the relevant status to a specific observer object, and send a change notification to all subscribed observers when the status changes within a specific topic;

3, Abstract Observer

Define an interface for all specific observers to update themselves when notified of the topic;

4. Concrete Observer

Implement the notification (receive) interface required by the abstract observer role to coordinate its own state with the topic state.

The sample



The namespace ObserverPattern contains the base class of abstract publishing house Publisher (theme), The class of China Machinery Press Machine, the class of Agriculture of China Agricultural Press, the reader interface IReader (observer), the specific observer Iori class and Jay class, and the book class Boo K. In addition, in order to make the code cleaner, Extentions extension class is introduced to facilitate the processing of books and reader information. This example shows how a reader can observe the status of a book published by a publisher and be notified when it is published.

public class Book {

    public string Name { get; set; }

    public Book(string name){ Name = name; }}Copy the code

A simple book class containing only a constructor and the book’s name attribute.

public interface IReader {

    void Receive(Publisher publisher, Book book);

}
Copy the code

The reader interface, which defines the public Receive contract, and gets publisher and book information.

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

Specific readers, Iori and Jay, one is my English name, the other is my idol.

public abstract class Publisher {
 
    private List<IReader> _readers = new List<IReader>();
 
    public string Name { get; set; }
    private const string LINE_BREAK =
        "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --" +
        "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --";
    // Break the text into 2 lines
 
    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); }}Copy the code

Abstract Publisher class, namely observed, which is the core base class of the whole observer pattern. A reference to the IReader list is first maintained internally, and the observer can be added (AttachReader) or removed (DetachReader). The Publish method notifies all observers when a publisher publishes a new book.

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); }}Copy the code
public class Agriculture : Publisher {

    public Agriculture(string name){ Name = name; }}Copy the code

The specific publishing houses Machine and Agriculture represent China Machinery Press and China Agriculture Press.

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 + "]"; }}Copy the code

A static extension method class that is exposed, where the ReaderName extension handles the namespace before the ReaderName and the nameof keyword is used to support refactoring. The BookName extension is used to enclose the book’s title.

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(); }}Copy the code

This is the code example of the caller, first create an instance of China Machine Press, then create two specific reader instances and subscribe, and finally publish the book “How the Steel is Tempered”, both readers can receive the publication notification. The following shows the cancellation of subscription and the release of China Agriculture Press, please see your own analysis. Here is the output of this case:

China Machine Press published [How the Steel Was Tempered] at 201807 -- 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 201807 -- 19.->Machine.OnPublish -------------------------------------------------------------------------------- Iori received [Jane  Eyre]from China Machine Press.
--------------------------------------------------------------------------------
China Agriculture Press published [Romance of the Three Kingdoms] at 201807 -- 19.
--------------------------------------------------------------------------------
Iori received [Romance of the Three Kingdoms] from China Agriculture Press.
Jay received [Romance of the Three Kingdoms] from China Agriculture Press.
--------------------------------------------------------------------------------
Copy the code

advantages

1. The observer mode establishes an abstract coupling between the observed and the observer. All that is known to the observer role is a list of concrete observers, each of which corresponds to the interface of an abstract observer. The observed does not know the inner details of each particular observer, only that they all have a common interface; 2. Since the observed and the observer are not tightly coupled, they can belong to different abstraction levels and comply with Richter’s substitution principle and dependency inversion principle.

disadvantages

1. If an observed object maintains a large number of observers, it will take a long time to notify all observers; 2. If there are cyclic dependencies between the observed, the observed may trigger cyclic calls between them, resulting in system crash; 3. Although the observer mode can let the observer know that the observed object has changed at any time, the observer mode has no corresponding mechanism to let the observer know how the observed object has changed.

Usage scenarios

1. Updating the state of one object requires synchronous updating of other objects, and the number of other objects is dynamically variable; 2. Objects only need to notify other objects of their updates and do not need to know the internal details of other objects.