65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 中的观察者模式

starIconstarIconstarIconstarIconstarIcon

5.00/5 (11投票s)

2022年3月7日

MIT

3分钟阅读

viewsIcon

20622

downloadIcon

305

C# 观察者模式教程文章

引言

这是一篇关于 C# 中观察者模式的教程文章。目标读者是中级及以上 C# 程序员。我们首先介绍经典的观察者模式,它是 GoF 模式之一,并在文献中经常被提及。

像 C# 这样的现代语言已经集成了事件机制,通过该机制,观察者模式实际上已集成到语言机制中。在 C# 中,观察者模式的现代用法实际上与事件机制的使用有关,而经典的观察者模式在 C# 中已变得过时。

事件机制实际上提供同步调用的事实经常被忽视,并且没有得到足够的强调。程序员通常会产生并行的错觉,但这不是现实,并且在当今的多核处理器世界中是一个重要的问题。

所提供的代码是教程,概念演示级别,为了简洁起见,不处理或显示所有变体/问题。

经典观察者模式

观察者模式,有时也称为主题-观察者模式,定义了对象之间的一对多依赖关系,以便当一个对象(Subject,主题)改变其状态时,所有依赖它的对象(Observers,观察者)都会得到通知并更新。 我们说 ObserverSubject“订阅”以获取 Subject 的状态更改通知。 简单来说,一个组件 (Subject) 通知其他组件 (Observers) 状态已更改。

虽然在 C# 中,Event 关键字已经集成,这是观察者模式的实现,但仍然可以决定推出自己的版本/实现。 以下是 GoF 文献中描述的经典观察者的外观。

类图

实现代码示例

public class Args
{
}

public abstract class ASubject
{
    protected List<IObserver> observersList = new List<IObserver>();

    public void AddObserver(IObserver observer)
    {
        observersList.Add(observer);
    }

    public void RemoveObserver(IObserver observer)
    {
        observersList.Remove(observer);
    }
}

public interface IObserver
{
    void Update(object subject, Args args);
}

public class Subject : ASubject
{
    public string SubjectState = null;

    public void NotifyObservers()
    {
        ArgsSubject args = new ArgsSubject();
        args.SubjectState = this.SubjectState;

        foreach (IObserver o in observersList)
        {
            o.Update(this, args);
        }
    }
}

public class Observer : IObserver
{
    private string name;
    private string observerState;

    public Observer(string name)
    {
        this.name = name;
    }

    public void Update(object s, Args args)
    {
        observerState = ((ArgsSubject)args).SubjectState;
        Console.WriteLine("Observer {0}'s new state is {1}",
            name, observerState);
    }
}

public class ArgsSubject : Args
{
    public string SubjectState = null;
}

class Client
{
    public static void Main(string[] args)
    {
        Subject s = new Subject();
        s.AddObserver(new Observer("1"));
        s.AddObserver(new Observer("2"));
        s.AddObserver(new Observer("3"));

        // Change subject state and notify observers
        s.SubjectState = "ABC123";
        s.NotifyObservers();

        Console.ReadLine();
    }
}

示例执行

C# 中的现代观察者模式 - 事件

C# 已经集成了 Event 关键字,它是观察者模式的实现。 C# 带来了自己的术语

  • Subject -> Event
  • Observer -> EventHandler (更准确地说,Observer.Notify() 方法映射到 EventHandler 方法,Observer 对象本身被认为是过时的)
  • SubjectStateInfo -> EventArgs(更准确地说,SubjectStateInfo 映射到从 EventArgs 继承的类的属性)
  • AddObserver() 方法 -> operator +=
  • RemoveObserver() 方法 -> operator -=

C# 文献建议使用具有特定签名的 EventHandler 委托,带有参数 (this, EventArgs),但 C# 语言中没有任何内容阻止您使用您喜欢的签名。

这是新解决方案的类图

这是代码

public class Subject
{
    public event EventHandler<EventArgsSubject> SubjectEvent;

    public string SubjectState;

    public void NotifyObservers()
    {
        EventArgsSubject args = new EventArgsSubject();
        args.SubjectState = this.SubjectState;

        // (1) 
        if (SubjectEvent != null)
        {
            SubjectEvent(this, args);
        }
    }
}

public class Observer
{
    private string name;
    private string observerState;

    public Observer(string name)
    {
        this.name = name;
    }

    public void Update(object subject, EventArgsSubject args)
    {
        observerState = args.SubjectState;
        Console.WriteLine("Observer {0}'s new state is {1}",
            name, observerState);
    }
}

public class EventArgsSubject : EventArgs
{
    public string SubjectState = null;
}

class Client
{
    public static void Main(string[] args)
    {
        Subject s = new Subject();
        s.SubjectEvent += (new Observer("1")).Update;
        s.SubjectEvent += (new Observer("2")).Update;
        s.SubjectEvent += (new Observer("3")).Update;

        // Change subject state and notify observers
        s.SubjectState = "ABC123";
        s.NotifyObservers();

        Console.ReadLine();
    }
}

这是示例执行

让我们提到,在 (1) 中,我们使用了一种在文献中很流行的方法。但是,从理论上讲,在检查 SubjectEvent 是否为 null 和调用事件之间,可能会出现“线程竞争”情况,某些其他线程可以从 SubjectEvent 中删除处理程序,这会导致 null 引用异常。 今天更流行的编写处理程序调用的方法是

SubjectEvent?.Invoke(this, args);

事件机制在单个线程上提供同步调用

需要强调的是,在调用中

if (SubjectEvent != null)
{
    SubjectEvent(this, args);
}

//or

SubjectEvent?.Invoke(this, args);

订阅的 EventHandler 是在单个线程上同步调用的。 这有一些不太明显的后果

  • EventHandler 按照它们订阅事件的顺序依次执行。
  • 这意味着在较早订阅的 EventHandler 中的对象/值比在其他 EventHandler 中的对象/值更早更新,这可能会对程序逻辑产生影响。
  • 对特定 EventHandler 的调用会阻塞线程,直到该 EventHandler 中的所有工作完成为止。
  • 如果在某个 EventHandler 中抛出异常,则在该 EventHandler 之后订阅的所有 EventHandler 都不会被执行。

结论

观察者模式是一种非常重要的模式,并且 C# 语言通过使用事件直接支持它。 尽管线程问题可能很棘手,但程序员必须对从线程角度来看通知机制的工作方式有很好的理解。

历史

  • 2022年3月7日:初始版本
  • 2022年9月11日:决定将文章分成两部分
© . All rights reserved.