观察者模式

摘要

本文以C#示例说明观察者模式的概念和应用场景。

定义

观察者模式(Observer Pattern <[əb'zɝvɚ] ['pætɚn]>, 有时又被称为发布&订阅(Publish&Subscribe)模式)是软件设计模式的一种。在此种模式中,一个目标对象管理所有相依于它的观察者对象,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实时事件处理系统。维基百科

观察者模式

解读

  • 关键词:发布-订阅
  • 应用场景:猎头招聘,日志系统,新用户注册
  • 被观察者知道观察者的存在,并能调用观察者提供的接口,比如求职者和猎头之间的关系;
  • 目的:解决观察者和信息发布者之间的耦合问题。

现实生活中的场景

  • 微信朋友圈
  • ...

下面用代码示例进行说明:

观察者模式

场景

日志消息的记录和打印,产生的字符串类型的日志消息分别在控制台/屏幕打印和记录文本/磁盘文件。

具体实现如下

using System;
using System.IO;

public interface ILogObserver
{
    int id { get; set; }
    void Insert(string message);
}

public class LogSubject
{
    // 观察者队列
    private List<ILogObserver> observers;

    public LogSubject()
    {
        observers = new List<ILogObserver>();    
    }

    // 观察者注册
    public void Register(ILogObserver obs)
    {
        if (!observers.Exists(ob=>ob.id==obs.id)
        {
            observers.add(obs); 
        }
    }

    // 观察者移除
    public void Remove(ILogObserver obs)
    {
        if (observers.Exists(ob=>ob.id==obs.id)
        {
            observers.Remove(obs); 
        }
    }

    // 调用观察者接口
    public void Notify(string message)
    {
        foreach (ILogObserver obs in observers)
        {
            obs.Insert(message);
        }
    }
}

/// <summary>控制台日志</summary>
public class ConsoleLog: ILogObserver
{

    public int id { get; set; }    

    public void Insert(string message)
    {
        System.Console.WriteLine(message);
    }
}

/// <summary>文本日志</summary>
public class TextLog: ILogObserver
{
    public int id { get; set; }    

    public void Insert(string message)
    {
        string filePath = "x:\log.txt";
        System.File.AppendLine(filePath, message);
    }
}


使用事件实现

在 .Net 中,可以利用事件和委托简化观察者模式的实现,上面的例子可以修改如下:


    // 定义委托
    public delegate void LogInsertedEventHandler<LogInsertEventArgs>(string message);

    // 被观察者
    public class LogInserter
    {
        public LogInsertedEventHandler<LogInsertEventArgs>  LogInserted;

        public void AddObserver(LogInsertedEventHandler<LogInsertEventArgs> handler)
        {
            LogInserted += handler;
        }

        public void RemoveObserver(LogInsertedEventHandler<LogInsertEventArgs> handler)
        {
            LogInserted -= handler;
        }

        public void Insert(string message) => LogInserted?.Invoke(message);
    }

    // 委托/事件参数
    public class LogInsertEventArgs : EventArgs
    {
        public string Message { get; set; }

        public LogInsertEventArgs(string message)
        {
            this.Message = message;
        }
    }

    // 订阅者接口
    public interface ILogSubscriber
    {
        void LogInserted(string message);
    }

    // 订阅者实现(控制台打印)
    public class ConsoleLogSubscriber : ILogSubscriber
    {
        public void LogInserted(string message)
        {
            Console.WriteLine($"日志打印到屏幕: {message}");
        }
    }

    // 订阅者实现(追加到文本)
    public class TextLogSubscriber : ILogSubscriber
    {
        public void LogInserted(string message)
        {
            Console.WriteLine($"日志追加到文本: {message}");
        }
    }

    // 调用

    class Program
    {
        static void Main(string[] args)
        {
            // 被观察者
            LogInserter ins = new LogInserter();

            // 观察者定义
            TextLogSubscriber textSub = new TextLogSubscriber();
            ConsoleLogSubscriber consoleSub = new ConsoleLogSubscriber();

            // 添加观察者
            ins.AddObserver(handler: new LogInsertedEventHandler<LogInsertEventArgs>(textSub.LogInserted));
            ins.AddObserver(handler: new LogInsertedEventHandler<LogInsertEventArgs>(consoleSub.LogInserted));

            // 产生日志
            ins.Insert("这是日志内容");

            Console.ReadLine();
        }
    }

输出如下:

日志追加到文本: 这是日志内容
日志打印到屏幕: 这是日志内容

观察者模式

好了,今天的观察者模式就总结到这里,大家继续回去写BUG吧