观察者模式(Observer)
定义
观察者模式又名“订阅-发布”模式,定义了一种对象之间的一对多的依赖,让多个观察者对象同时监听某一个主题对象,当主题对象发生状态变化时,它的所有依赖者(即观察者)都会收到通知并更新自己的行为。
适用场景
微信中的订阅号、订阅博客和QQ微博中的关注好友,这些都属于观察者模式的应用。
UML 图
可以看出,在观察者模式的结构图中有以下角色:
- 抽象主题角色(Subject):抽象主题把所有观察者的引用保存在一个列表中,并提供增加和删除观察者对象的操作,抽象主题角色又叫做抽象被观察者角色,一般由抽象类或接口实现。
- 抽象观察者角色(Observer):为所有具体观察者定义一个接口,在得到主题通知时更新自己,一般由抽象类或接口实现。
- 具体主题角色(ConcreateSubject):实现抽象主题接口,具体主题角色又叫做具体被观察者角色。
- 具体观察者角色(ConcreateObserver):实现抽象观察者角色所要求的接口,以便使自身状态与主题的状态相协调。
代码实现
想象客户订阅报纸的场景,顾客可是时时看到最新的报纸,而报纸可以作为一个主题,即subject,顾客就是观察者,需要及时拿到最新的报纸。
namespace ObserverDesignPattens
{
public abstract class Customer
{
public abstract string Update(NewspaperOffice subject);
}
public class CustomerA:Customer
{
public override string Update(NewspaperOffice subject)
{
return $"A {subject.Message} {subject.Name}";
}
}
public class CustomerB : Customer
{
public override string Update(NewspaperOffice subject)
{
return $"B {subject.Message} {subject.Name}";
}
}
public class NewspaperOffice
{
private readonly List<Customer> _customers=new List<Customer>();
public string Name { get; set; }
public string Message { get; set; }
public NewspaperOffice(string name,string message)
{
this.Name = name;
this.Message = message;
}
public void AddCustomer(Customer customer)
{
this._customers.Add(customer);
}
public void RemoveCustomer(Customer customer)
{
this._customers.Remove(customer);
}
public ArrayList ComeNewspaper()
{
var result = new ArrayList();
foreach (var customer in _customers)
{
if (customer != null) { result.Add(customer.Update(this)); }
}
return result;
}
}
}
测试代码如下:
namespace DesignPattens.Tests
{
[TestClass]
public class ObserverDesignPattensTest
{
[TestMethod]
public void Observer_should_work()
{
var newspaperOffice=new NewspaperOffice("newspaper","subscribed");
var a=new CustomerA();
var b=new CustomerB();
newspaperOffice.AddCustomer(a);
newspaperOffice.AddCustomer(b);
var result = newspaperOffice.ComeNewspaper();
Assert.AreEqual(2,result.Count);
Assert.IsTrue(result.Contains("A subscribed newspaper"));
Assert.IsTrue(result.Contains("B subscribed newspaper"));
}
}
}
总结
相应的一些语言如C#和Java也内置了观察者模式的实现,Java采用了 observable 类,有兴趣的朋友可以看看其实现源码,虽然相似但考虑了线程安全,包装了 synchronized
关键字;C# 中拖过委托和代理的方式实现了观察者模式,其代码如下:
namespace CharpObserverDesignPattens
{
public delegate string NotifyEventHandler();
public abstract class Customer
{
public abstract string Update();
}
public class CustomerA : Customer
{
private readonly NewspaperOffice _newspaperOffice;
public string Subject { get; set; }
public CustomerA(NewspaperOffice newspaperOffice)
{
this._newspaperOffice = newspaperOffice;
}
public override string Update()
{
this.Subject = "A";
return $"{Subject} {_newspaperOffice.Message} {_newspaperOffice.Name}";
}
}
public class CustomerB : Customer
{
private readonly NewspaperOffice _newspaperOffice;
public string Subject { get; set; }
public CustomerB(NewspaperOffice newspaperOffice)
{
this._newspaperOffice = newspaperOffice;
}
public override string Update()
{
this.Subject = "B";
return $"B {_newspaperOffice.Message} {_newspaperOffice.Name}";
}
}
public class NewspaperOffice
{
public event NotifyEventHandler NotifyEvent;
public string Name { get; set; }
public string Message { get; set; }
public NewspaperOffice(string name, string message)
{
this.Name = name;
this.Message = message;
}
public void AddCustomer(NotifyEventHandler no)
{
NotifyEvent+=no;
}
public void RemoveCustomer(NotifyEventHandler no)
{
NotifyEvent-=no;
}
public void ComeNewspaper()
{
NotifyEvent?.Invoke();
}
}
}
以下是测试代码:
namespace DesignPattens.Tests
{
[TestClass]
public class SecondObserverDesignPattensTest
{
[TestMethod]
public void Observer_should_work()
{
var newspaperOffice=new NewspaperOffice("newspaper","subscribed");
var a=new CustomerA(newspaperOffice);
var b=new CustomerB(newspaperOffice);
newspaperOffice.AddCustomer(new NotifyEventHandler(a.Update));
newspaperOffice.AddCustomer(new NotifyEventHandler(b.Update));
newspaperOffice.ComeNewspaper();
Assert.IsTrue(a.Subject.Equals("A"));
Assert.IsTrue(b.Subject.Equals("B"));
}
}
}
由此可以看出语言内部实现的方式精简了代码。