满纸荒唐言,一把心酸泪,都云作者痴,谁解其中味。 技术博客 心情随笔
设计模式:观察者
2025/4/14 201

导航

1前言

2应用场景

3观察者模式的构成

4实际业务代码示例

5总结

1 前言

观察者模式定义了对象之间一对多的依赖关系,当一个对象发生变化时,多个依赖它的对象都会收到通知。观察者模式也是一种通信范式,不光存在于软件开发中,在日常生活中也非常常见,比如拍卖会上的竞拍者观察拍卖师的报价后制定自己的出价策略,粉丝在微博上关注自己喜欢的明星后收到明星的动态推送等。因为观察者模式在开发中使用的非常频繁,大部分开发语言和框架均原生支持观察者模式,比如C#中的事件、QT中的信号槽等。来源:https://www.wubayue.com

2 应用场景

在软件开发中,对象之间的交互,大致可分为两种方式,一种是方法调用,比如朋友叫我去打球,我马上响应:

对象之间调用

另一种是订阅,比如朋友约我去打球,我可以先处理手上的事情,忙完后再响应预约:

对象之间订阅

在观察者模式中,我是被观察者,我的朋友是观察者,当我的状态发生变化时,我的朋友会收到我的通知。

这种交互也称为发布-订阅,我的朋友向我发起了预约,当达到约定条件时,我通知我的朋友。来源:https://www.wubayue.com

3 观察者模式的构成

观察者模式类图

抽象目标

抽象目标(Subject)也称为被观察者,多个观察者观察一个目标,比如多个朋友都可以约我打球。目标知道所有的观察者,比如我知道所有约我打球的朋友们。

目标提供注册和移除观察者对象的接口,比如朋友们通过我的电话或微信可以向我预约或取消打球。

// 抽象目标
public interface ISubject
{
    // 增加观察者
    void Attach(IObserver observer);

    // 移除观察者
    void Detach(IObserver observer);

    // 通知观察者
    void Notify();
} 

具体目标

记录所有观察者。比如我会将所有约我打球的朋友记在脑海中。

当状态发生变化时,向观察者们发送通知。比如触发了“忙完”这个状态,我就会通知与我有约的朋友们。

// 具体目标
public class Subject : ISubject
{
    private List<IObserver> observers = new List<IObserver>();

    // 增加观察者
    public void Attach(IObserver observer)
    {
        this.observers.Add(observer);
    }

    // 移除观察者
    public void Detach(IObserver observer)
    {
        this.observers.Remove(observer);
    }

    // 通知观察者
    public void Notify()
    {
        foreach (var observer in observers)
            observer.ReceiveNotify("吴八月", "忙完了,去老地方打球");
    }
} 

抽象观察者

抽象观察者中只定义了一个接收通知的方法。

// 抽象观察者
public interface IObserver
{
    // 接收通知
    void ReceiveNotify(object sender, object args);
} 

具体观察者

观察者的具体实现,接收通知并处理业务逻辑。

// 具体观察者
public class Observer : IObserver
{
    // 接收通知
    public void ReceiveNotify(object sender, object args)
    {
        Console.WriteLine($"收到了{sender}通知:{args}");
    }
} 

示例代码来源:https://www.wubayue.com

static void Main(string[] args)
{
    // 目标
    Observer.ISubject subject = new Observer.Subject();
    // 为目标增加观察者
    Observer.IObserver observer = new Observer.Observer();
    subject.Attach(observer);
    // 目标向观察者发送通知
    subject.Notify();
} 

4 实际业务代码示例

在实际业务代码中,通常不需要自己完整的来实现观察者模式。因为大部分的编程语言和框架都支持观察者模式,比如在C#中可通过事件优雅的使用观察者模式。

抽象目标

// 抽象目标
public interface ISubject
{
    // 定义我的忙碌状态变更事件
    event EventHandler<string> BusyStatusChanged;
} 

具体目标

// 具体目标
public class Subject : ISubject
{
    public Subject() 
    {
        // 模拟一段时间后忙碌完成,并通知观察者            
        Thread t = new Thread(() =>
        {
            int timer = 0;
            while (true)
            {
                Console.WriteLine(timer++);
                Thread.Sleep(1000);
                if (timer == 10)
                {
                    if (BusyStatusChanged != null)
                        BusyStatusChanged("吴八月", "忙完了,去老地方打球");
                }
            }
        });
        t.IsBackground = true;
        t.Start();
    }

    // 我的忙碌状态变更事件
    public event EventHandler<string> BusyStatusChanged;
} 

观察者来源:https://www.wubayue.com

// 观察者
public class Observer
{
    public Observer() 
    {
        // 目标
        ISubject subject = new Subject();
        // 订阅目标事件
        subject.BusyStatusChanged += Subject_BusyStatusChanged; ;
    }

    // 接收事件通知
    private void Subject_BusyStatusChanged(object? sender, string e)
    {
        Console.WriteLine($"收到了{sender}通知:{e}");
    }
} 

5 总结

优点

观察者模式专为处理订阅类的业务场景而设计,具有简洁高效的特点。如果不使用观察者模式而使用传统的轮询,在效率上将是难以忍受的,比如朋友想约我打球又看见我在忙,于是每秒轮询一次问我忙完了没,这就不再是打不打球的问题,而是朋友还有没有的做的问题了。

缺点

当观察者过多时,会存在效率问题。比如有三五个朋友约我打球,我挨个通知他们还好,但当要通知三五十个朋友时,让我来挨个通知就是一件繁琐的事情了。

数据传递的推模式与拉模式

目标向观察者发送通知时,可以附带数据,此为数据的推模式;也可以不附带数据,观察者在收到通知后自行获取数据,此为拉模式;通常由数据的大小与复杂程度决定使用推模式或拉模式,可根据实际情况灵活运用。来源:https://www.wubayue.com

<全文完>