找回密码
 立即注册
首页 业界区 业界 C#设计模式之观察者模式

C#设计模式之观察者模式

赘暨逢 4 天前
前言

观察者(Observer)模式也称发布-订阅(Publish-Subscribe)模式,定义了对象间一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
观察者模式的图解如下所示:
1.png

Subject(目标):
目标知道它的观察者。可以有任意多个观察者观察同一个目标。
目标提供了注册和删除观察者对象的接口。
Observer(观察者):
为那些在目标发生改变时需获得通知的对象定义一个更新接口。
ConcreteSubject(具体目标):
将有关状态存入各ConcreteObserver对象。
当它的状态发生改变时,向它的各个观察者发出通知。
ConcreteObserver(具体观察者):
维护一个指向ConcreteSubject对象的引用。
存储有关状态,这些状态应与目标的状态保存一致。
实现Observer的更新接口以使自身状态与目标的状态保持一致。
不使用事件的示例

接下来我将根据上面的图解写一个C#观察者模式的例子,刚开始这个例子没有使用事件,在后面一个例子中使用事件。
首先来看看Subject(目标):
  1. //主题接口
  2. public interface ISubject
  3. {
  4.      public void Attach(Observer observer);
  5.      public void Detach(Observer observer);
  6.      public void Notify();
  7.      public string? SubjectState { get; set; }
  8. }
复制代码
这里我使用的是接口,里面有Attach、Detach和Notify方法的声明,还有SubjectState属性的声明。
接下来看看ConcreteSubject(具体目标):
  1. public class School : ISubject
  2. {
  3.      private IList<Observer> observers = new List<Observer>();
  4.      public string? SubjectState { get; set; }
  5.      public void Attach(Observer observer)
  6.      {
  7.          observers.Add(observer);
  8.      }
  9.      public void Detach(Observer observer)
  10.      {
  11.          observers.Remove(observer);
  12.      }
  13.      public void Notify()
  14.      {
  15.          foreach (var observer in observers)
  16.          {
  17.              observer.Update();
  18.          }
  19.      }
  20. }
复制代码
这里我以学校发通知为例,School类实现了ISubject接口,有一个观察者的列表,在Attach方法中添加一个观察者,在Detach方法中移除一个观察者。在Notify方法中遍历观察者的列表,让每一个观察者都执行Update方法。
现在来看看Observer(观察者):
  1. public abstract class Observer
  2. {
  3.      protected string? name;
  4.      protected ISubject? subject;
  5.      public Observer(string name,ISubject subject)
  6.      {
  7.          this.name = name;
  8.          this.subject = subject;
  9.      }
  10.      public abstract void Update();
  11. }
复制代码
这是一个抽象类,包含有一个抽象的Update方法。
再看看ConcreteObserver(具体观察者):
  1. public class Student : Observer
  2. {
  3.      public Student(string name,ISubject subject) : base(name,subject)
  4.      {
  5.      }
  6.      public override void Update()
  7.      {
  8.          Console.WriteLine($"{name}接收到来自学校的通知:{subject?.SubjectState},
  9.                            时间:{DateTime.Now}\r\n");
  10.      }
  11. }
复制代码
这里我以学生接收来自学校的消息为例,Student类继承自Observer抽象类,并重写了Update方法。
最后来看看怎么使用观察者模式:
  1. static void Main(string[] args)
  2. {
  3.      School school = new School();
  4.      Student student1 = new Student("小王", school);
  5.      Student student2 = new Student("小明", school);
  6.      Student student3 = new Student("小红", school);
  7.      school.Attach(student1);
  8.      school.Attach(student2);
  9.      school.Attach(student3);
  10.      school.SubjectState = "学校放假了";
  11.      school.Notify();
  12.    
  13.      school.Detach(student3);
  14.      Task.Delay(3000).Wait();
  15.      school.SubjectState = "学校开学了";
  16.      school.Notify();
  17.      Console.ReadLine();
  18. }
复制代码
创建一个School对象,三个Student对象。
  1.      school.Attach(student1);
  2.      school.Attach(student2);
  3.      school.Attach(student3);
复制代码
表示将student1、student2、student3添加到school中的观察者列表中。
  1. school.SubjectState = "学校放假了";
  2. school.Notify();
复制代码
设置school中SubjectState属性的值,然后调用school的Notify方法。
  1. school.Detach(student3);
复制代码
将student3从school的观察者列表中移除。
  1. Task.Delay(3000).Wait();
复制代码
等待3秒。
  1. school.SubjectState = "学校开学了";
  2. school.Notify();
  3. Console.ReadLine();
复制代码
重新设置school中SubjectState属性的值,然后再调用school的Notify方法。
运行结果如下所示:
2.png

学校放假了,小王、小明和小红都接收到了学校的通知。由于后面小红被移出了观察者列表,因此学校开学了的消息小红没有接收到。
使用事件的示例

C#中可以通过事件来使用观察者模式,接下来我将以一个示例加以说明。
自定义事件数据类:
  1. public class SendMessageArgs : EventArgs
  2. {
  3.      public string? Message { get; set; }
  4.      public DateTime DateTime { get; set; }
  5.      public SendMessageArgs(string? message)
  6.      {
  7.          Message = message;
  8.          DateTime = DateTime.Now;
  9.      }
  10. }
复制代码
Person类:
  1. public class Person
  2. {
  3.      public string? Name { get; set; }
  4.      public event EventHandler<SendMessageArgs>? SendMessageEvent;
  5.      public Person(string? name)
  6.      {
  7.          Name = name;
  8.      }
  9.      public void SendMessage(string message)
  10.      {
  11.          SendMessageArgs sendMessageArgs = new SendMessageArgs(message);
  12.          
  13.          SendMessageEvent?.Invoke(this, sendMessageArgs);
  14.      }
  15.      public void ShowMessage(object? sender,SendMessageArgs e)
  16.      {
  17.          Person? person = (Person?)sender;
  18.          Console.WriteLine($"{this.Name}:收到来自{person?.Name}的消息:{e.Message},时间:{e.DateTime}\r\n");
  19.      }
  20.      public void Subscribe(Person person)
  21.      {
  22.          person.SendMessageEvent += ShowMessage;
  23.      }
  24.      public void UnSubscribe(Person person)
  25.      {
  26.          person.SendMessageEvent -= ShowMessage;
  27.      }
  28. }
复制代码
Person类中的SendMessage方法会触发事件,ShowMessage方法是事件处理程序,Subscribe方法可以订阅事件,UnSubscribe方法可以取消订阅事件。
现在来看看是怎么使用:
  1. static void Main(string[] args)
  2. {
  3.     Person Trump = new Person("川普");
  4.     Person Biden = new Person("拜登");
  5.     Person person1 = new Person("person1");
  6.     Person person2 = new Person("person2");
  7.     Person person3 = new Person("person3");
  8.     person1.Subscribe(Trump);
  9.     person2.Subscribe(Trump);
  10.     person3.Subscribe(Trump);
  11.     person1.Subscribe(Biden);
  12.     Trump.SendMessage("Nobody knows ... better than me!!!");
  13.     Task.Delay(2000).Wait();
  14.     Biden.SendMessage("I don't believe it!!!");
  15.     person3.UnSubscribe(Trump);
  16.     Task.Delay(2000).Wait();
  17.     Trump.SendMessage("Make ... Great Again!!!");
  18.     Console.ReadLine();         
  19. }
复制代码
创建了5个Person对象,分别为Trump、Biden、person1、person2、person3。
person1、person2、person3订阅了Trump,person1订阅了Biden。
Trump发了一条消息,然后过了2秒,Biden也发了一条消息。
person3不再订阅Trump,过了2秒,Trump又发消息了。
运行结果如下所示:
3.png

由于person1、person2、person3订阅了Trump,所以可以收到来自Trump的消息。
由于person1订阅了Biden,所以可以收到来自Biden的消息。
后面person3退订了Trump,所以只有person1、person2能收到来自Trump的消息。
总结

以上使用C#分别创建了不通过事件与通过事件的示例,介绍了在C#中如何使用观察者模式,希望对你有所帮助。
参考

1、《Head First 设计模式(中文版)》
2、《大话设计模式》
3、《设计模式:可复用面向对象软件的基础》
4、YouTube [Design Patterns: C# Pub-Sub Simple Twitter example]

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册