导航
1前言
2应用场景
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;
}
// 观察者
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
<全文完>