在现实生活中,处处可见观察者模式,例如,微信中的订阅号,只要对订阅号进行关注的客户端,如果订阅号有什么更新,就会直接推送给订阅了的用户。这就是观察者模式的一种应用。
1.在多个对象之间需要有一种一对多的依赖关系,并且不希望关键对象和被依赖对象之间存在强耦合关系。
2.需要实现实时通信,如事件驱动的程序等。
3.需要通知多个对象,但又不知道这些对象的确切数量和类型时。
4.需要将各个对象解耦开来,使得它们的改动不会影响到其他对象。
在C#中,使用观察者模式的关键是要定义一个接口,包含一个Update()方法。这个方法表示观察者需要在被通知时执行的操作。
接口代码示例:
//观察者接口
public interface IObserver
{
void Update();
}
然后,在被观察者对象中定义一个List类型的观察者列表,并提供添加删除观察者的方法。被观察者对象的代码示例:
//被观察者抽象类
public abstract class Subject
{
private List<IObserver> _observers = new List<IObserver>();
//添加观察者
public void Attach(IObserver observer)
{
_observers.Add(observer);
}
//移除观察者
public void Detach(IObserver observer)
{
_observers.Remove(observer);
}
//通知观察者
public void Notify()
{
foreach (var observer in _observers)
{
observer.Update();
}
}
}
//被观察者实现类
public class ConcreteSubject:Subject
{
}
具体的观察者对象需要实现IObserver接口中的Update()方法,以便在被通知时能够执行相应的操作。观察者对象的代码示例:
//具体观察者类1
public class ConcreteObserverA : IObserver
{
public void Update()
{
Console.WriteLine("ConcreteObserverA received the message.");
}
}
//具体观察者类2
public class ConcreteObserverB : IObserver
{
public void Update()
{
Console.WriteLine("ConcreteObserverB received the message.");
}
}
最后,我们可以在客户端中创建具体的被观察者对象和观察者对象,并将观察者对象添加到被观察者对象的观察者列表中,从而实现观察者模式的功能。
class Client
{
static void Main(string[] args)
{
//创建被观察者对象
Subject subject = new ConcreteSubject();
//创建两个具体观察者对象并添加到被观察者对象的观察者列表中
IObserver observerA = new ConcreteObserverA();
IObserver observerB = new ConcreteObserverB();
subject.Attach(observerA);
subject.Attach(observerB);
//通知观察者对象
subject.Notify();
}
}
观察者模式在显示生活中也有类似的例子,比如:我们订阅银行短信业务,当我们账户发生改变,我们就会收到相应的短信。类似的还有微信订阅号,今天我们就以银行给我发送短信当我们账户余额发生变化的时候为例来讲讲观察者模式的实现,很简单,现实生活正例子也很多,理解起来也很容易。我们看代码吧,实现代码如下:
//银行短信系统抽象接口,是被观察者--该类型相当于抽象主体角色Subject
public abstract class BankMessageSystem
{
protected IList<Depositor> observers;
//构造函数初始化观察者列表实例
protected BankMessageSystem()
{
observers = new List<Depositor>();
}
//增加预约储户
public abstract void Add(Depositor depositor);
//删除预约储户
public abstract void Delete(Depositor depositor);
//通知储户
public void Notify()
{
foreach (Depositor depositor in observers)
{
if (depositor.AccountIsChanged)
{
depositor.Update(depositor.Balance, depositor.OperationDateTime);
//账户发生了变化,并且通知了,储户的账户就认为没有变化
depositor.AccountIsChanged = false;
}
}
}
}
//北京银行短信系统,是被观察者--该类型相当于具体主体角色ConcreteSubject
public sealed class BeiJingBankMessageSystem : BankMessageSystem
{
//增加预约储户
public override void Add(Depositor depositor)
{
//应该先判断该用户是否存在,存在不操作,不存在则增加到储户列表中,这里简化了
observers.Add(depositor);
}
//删除预约储户
public override void Delete(Depositor depositor)
{
//应该先判断该用户是否存在,存在则删除,不存在无操作,这里简化了
observers.Remove(depositor);
}
}
//储户的抽象接口--相当于抽象观察者角色(Observer)
public abstract class Depositor
{
//状态数据
private string _name;
private int _balance;
private int _total;
private bool _isChanged;
//初始化状态数据
protected Depositor(string name, int total)
{
this._name = name;
this._balance = total;//存款总额等于余额
this._isChanged = false;//账户未发生变化
}
//储户的名称,假设可以唯一区别的
public string Name
{
get { return _name; }
private set { this._name = value; }
}
public int Balance
{
get { return this._balance; }
}
//取钱
public void GetMoney(int num)
{
if (num <= this._balance && num > 0)
{
this._balance = this._balance - num;
this._isChanged = true;
OperationDateTime = DateTime.Now;
}
}
//账户操作时间
public DateTime OperationDateTime { get; set; }
//账户是否发生变化
public bool AccountIsChanged
{
get { return this._isChanged; }
set { this._isChanged = value; }
}
//更新储户状态
public abstract void Update(int currentBalance, DateTime dateTime);
}
//北京的具体储户--相当于具体观察者角色ConcreteObserver
public sealed class BeiJingDepositor : Depositor
{
public BeiJingDepositor(string name, int total) : base(name, total) { }
public override void Update(int currentBalance, DateTime dateTime)
{
Console.WriteLine(Name + ":账户发生了变化,变化时间是" + dateTime.ToString() + ",当前余额是" + currentBalance.ToString());
}
}
// 客户端(Client)
class Program
{
static void Main(string[] args)
{
//我们有了三位储户,都是武林高手,也比较有钱
Depositor huangFeiHong = new BeiJingDepositor("黄飞鸿", 3000);
Depositor fangShiYu = new BeiJingDepositor("方世玉", 1300);
Depositor hongXiGuan = new BeiJingDepositor("洪熙官", 2500);
BankMessageSystem beijingBank = new BeiJingBankMessageSystem();
//这三位开始订阅银行短信业务
beijingBank.Add(huangFeiHong);
beijingBank.Add(fangShiYu);
beijingBank.Add(hongXiGuan);
//黄飞鸿取100块钱
huangFeiHong.GetMoney(100);
beijingBank.Notify();
//黄飞鸿和方世玉都取了钱
huangFeiHong.GetMoney(200);
fangShiYu.GetMoney(200);
beijingBank.Notify();
//他们三个都取了钱
huangFeiHong.GetMoney(320);
fangShiYu.GetMoney(4330);
hongXiGuan.GetMoney(332);
beijingBank.Notify();
Console.Read();
}
}
当我们订阅了书社的信息,当书社有新书发布的时候,就通知我们
//事件发布者
public class Publisher
{
public string Name { get; set; }
//定义事件
public event EventHandler<Book> PublishBookEvent;
//定义调用事件的方法
//当事件发布的时候,通知订阅者
public void OnPublish(Book book)
{
//如果实际处理的业务中,不需要入参,object sender 和EventArg e 都可以传入null
PublishBookEvent?.Invoke(this, book);
}
}
//事件订阅者1
public class Subscriber1
{
public string Name { get; set; }
//定义 订阅方法,当触发发布事件的时候就调用该方法
public void SubscribeBookInfo(object sender, Book book)
{
if (sender is Publisher publisher)
{
Console.WriteLine($"发布者:{publisher.Name},订阅者{Name}:订阅了{book.Name}");
}
}
}
//事件订阅者2
public class Subscriber2
{
public string Name { get; set; }
public void SubscribeBookInfo(object sender, Book book)
{
if (sender is Publisher publisher)
{
Console.WriteLine($"发布者:{publisher.Name},订阅者{Name}:订阅了{book.Name}");
}
}
}
//EventArgs是EventHandler中传入参数的基类,因此需要传递参数的时候,参数都需要继承自EventArgs
public class Book : EventArgs
{
public string Name { get; set; }
}
订阅事件:
static void Main(string[] args)
{
//发布者
Publisher publisher = new Publisher() { Name = "李发布" };
//订阅者1 订阅事件
publisher.PublishBookEvent += new Subscriber1() { Name = "张订阅" }.SubscribeBookInfo!;
//订阅者2 订阅事件
publisher.PublishBookEvent += new Subscriber2() { Name = "王订阅" }.SubscribeBookInfo!;
//发布者发布事件
publisher.OnPublish(new Book() { Name = "平凡的世界" });
Console.ReadLine();
}
优点:
缺点:
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。
参考资料:
C#设计模式之十六观察者模式(Observer Pattern)【行为型】
C#设计模式(17)——观察者模式(Observer Pattern)
C#设计模式15——观察者模式的写法