老板回来,我不知道——观察者模式

继上一篇“好菜每回味不同——建造者模式”后,本文继续讲解《大话设计模式》第14章“老板回来,我不知道——观察者模式”。喜欢本书请到各大商城购买原书,支持正版。

大话设计模式前期读书系列:

本文正式开始


1 老板回来?我不知道!

时间:4月12日21点 地点:小菜大鸟住所的客厅 人物:小菜、大鸟

小菜对大鸟说:“今天白天真的笑死人了,我们一同事在上班期间看股票行情,被老板当场看到,老板很生气,后果很严重呀。”

“最近股市这么火,也应该可以理解的,你们老板说不定也炒股。”

“其实最近项目计划排得紧,是比较忙的。而最近的股市又特别的火,所以很多人都在偷偷地通过网页看行情。老板时常会出门办事,于是大家就可以轻松一些,看看行情,几个人聊聊买卖股票的心得什么的,但是一不小心,老板就会回来,让老板看到工作当中做这些总是不太好,你猜他们想到怎么办?”

“只能小心点,那能怎么办?”

“我们公司前台秘书是一个小美眉,她的名字叫童子喆,因为平时同事们买个饮料或零食什么的,都拿一份孝敬于她,所以关系比较好,现在他们就请小子喆帮忙,如果老板出门后回来,就一定要打个电话进来,大家也好马上各就各位,这样就不会被老板发现问题了。”

“哈,好主意,老板被人卧了底,这下你们那些人就不怕被发现了。”

“是呀,只要老板进门,子喆拨个电话给同事中的一个,所有人就都知道老板回来了。这种做法屡试不爽。”

“那怎么还会有今天被发现的事?”

“今天是这样的,老板出门后,大家开始个个都打开股票行情查看软件,然后还聚在一起讨论着‘大盘现在如何’,‘你的股票抛了没有’等事。这时老板回来后,并没有直接走进去,而是对子喆交待了几句,可能是要她打印些东西,并叫她跟老板去拿材料,这样子喆就根本没有任何时间去打电话了。”

“哈,这下完了。”

“是呀,老板带着子喆走进了办公室的时候,办公室一下子从热闹转向了安静,好几个同事本是聚在一起聊天的,赶快不说话了,回到自己的座位上,最可怜的是那个背对大门的同事——魏关姹,他显然不知道老板回来了,竟然还叫了一句‘我的股票涨停了哦。’,声音很大,就当他兴奋的转过身想表达一下激动的心情时,却看到了老板愤怒的面孔和其他同事同情的眼神。”

“幸运却又倒霉的人,谁叫他没看到老板来呢。”

“但我们老板很快恢复了笑容,平静地说道:‘魏关姹,恭喜发财呀,你是不是考虑请我们大家吃饭哦。’魏关姹面红耳赤地说,‘老板,实在对不起!以后不会了。’‘以后工作时还是好好工作吧。大家都继续工作吧。’老板没再说什么,就去忙事情去了。”

“啊,就这样结束了﹖我还当他会拿魏关姹做典型,好好批评一顿呢。不过回过头来想想看,你们老板其实很厉害,这比直接批评来得更有效,大家都是明白人,给个面子或许都能下得了台,如果真的当面批评,或许魏关姹就干不下去了。”

“是的,生气却不发作,很牛。”

2 双向耦合的代码

“你说的这件事的情形,是一个典型的观察者模式。”大鸟说,“你不妨把其间发生的事写成程序看看。”

“哦,好的,我想想看。”小菜开始在纸上画起来。半分钟后,小菜给了大鸟程序。

前台秘书类

/// <summary>
/// 前台秘书类
/// </summary>
class Secretary
{
  //同事列表
  private IList<StockObserver> observers = new List<StockObserver>();
  private string action;

  // 增加
  // 就是有几个同事请前台帮忙,于是就给集合增加几个对象
  public void Attach(StockObserver observer)
  {
    observers.Add(observer);
  }

  // 通知
  // 待老板来时,就给所有的登记的同事们发通知,“老板来了”
  public void Notify()
  {
    foreach (StockObserver o in observers)
    {
      o.Update();
    }
  }

  // 前台状态
  // 前台通过电话,所说的话或所做的事
  public string SecretaryAction
  {
    get { return action; }
    set { action = value; }
  }
}

看股票同事类

/// <summary>
/// 看股票同事类
/// </summary>
class StockObserver
{
  private string name;
  private Secretary sub;

  public StockObserver(string name, Secretary sub)
  {

    this.name = name;
    this.sub = sub;
  }

  public void Update()
  {
    Console.WriteLine($"{sub.SecretaryAction}{name}关闭股票行情,继续工作! ");
  }
}

客户端程序如下:

class Program
{
  static void Main(string[] args)
  {
    // 前台小姐童子喆
    Secretary tongzizhe = new Secretary();

    // 看股票的同事
    StockObserver tongshil = new StockObserver("魏关姹", tongzizhe);
    StockObserver tongshi2 = new StockObserver("易管查", tongzizhe);

    // 前台记下了两位同事
    tongzizhe.Attach(tongshil);
    tongzizhe.Attach(tongshi2);

    //发现老板回来
    tongzizhe.SecretaryAction = "老板回来了! ";

    //通知两个同事
    tongzizhe.Notify();

    Console.Read();
  }
}

输出如下:

老板回来了! 魏关姹关闭股票行情,继续工作!
老板回来了! 易管查关闭股票行情,继续工作!

“写得不错,把整个事情都包括了。现在的问题是,你有没有发现,这个‘前台’类和这个‘看股票者’类之间怎么样?”

“嗯,你是不是指互相耦合﹖我写的时候就感觉到了,前台类要增加观察者,观察者类需要前台的状态。”

“对呀,你想想看,如果观察者当中还有人是想看NBA的网上直播(由于时差关系,美国NBA篮球比赛通常都是在北京时间的上午开始),你的‘前台’类代码怎么办?”

“那就得改动了。”

“你都发现这个问题了,你说该怎么办?想想我们的设计原则?”

“我就知道,你又要提醒我了。首先开放-封闭原则,修改原有代码就说明设计不够好。其次是依赖倒转原则,我们应该让程序都依赖抽象,而不是相互依赖。OK,我去改改,应该不难的。”

3 解耦实践一

半小时后,小菜给出了第二版。

增加了抽象的观察者

/// <summary>
/// 抽象的观察者
/// </summary>
abstract class Observer
{
  protected string name;
  protected Secretary sub;

  public Observer(string name, Secretary sub)
  {
    this.name = name;
    this.sub = sub;
  }

  public abstract void Update();
}

增加了两个具体的观察者

/// <summary>
/// 看股票的同事
/// </summary>
class StockObserver : Observer
{
  public StockObserver(string name, Secretary sub) : base(name, sub)
  {

  }

  public override void Update()
  {
    Console.WriteLine($"{sub.SecretaryAction} {name}关闭股票行情,继续工作! ");
  }
}

/// <summary>
/// 看NBA的同事
/// </summary>
class NBAObserver : Observer
{
  public NBAObserver(string name, Secretary sub) : base(name, sub)
  {

  }

  public override void Update()
  {
    Console.WriteLine($"{sub.SecretaryAction} {name}关闭NBA直播,继续工作! ");
  }
}

“这里让两个观察者去继承‘抽象观察者’,对于‘Update(更新)’的方法做重写操作。”

“下面是前台秘书类的编写,把所有的与具体观察者耦合的地方都改成了‘抽象观察者’。”

/// <summary>
/// 前台秘书类
/// </summary>
class Secretary
{
  // 同事列表
  private IList<Observer> observers = new List<Observer>();
  private string action;


  // 增加
  // 针对抽象编程,减少了与具体类的耦合
  public void Attach(Observer observer)
  {
    observers.Add(observer);
  }


  // 减少
  // 针对抽象编程,减少了与具体类的耦合
  public void Detach(Observer observer)
  {
    observers.Remove(observer);
  }

  //通知
  public void Notify()
  {
    foreach (Observer o in observers)
    {
      o.Update();
    }
  }

  //前台状态
  public string SecretaryAction
  {
    get { return action; }
    set { action = value; }
  }
}

客户端代码同前面一样。

“小菜,你这样写只完成一半呀。”

“为什么,我不是已经增加了一个‘抽象观察者’了吗?”

“你小子,考虑问题为什么就不能全面点呢,你仔细看看,在具体观察者中,有没有与具体的类耦合的?”

“嗯?这里有什么?哦!我明白了,你的意思是‘前台秘书’是一个具体的类,也应该抽象出来。”

“对呀,你想想看,你们公司最后一次,你们的老板回来,前台来不及电话了,于是通知大家的任务变成谁来做?”

“是老板,对的,其实老板也好,前台也好,都是具体的通知者,这里观察者也不应该依赖具体的实现,而是一个抽象的通知者。”

“另外,就算是你们的前台,如果某一个同事和她有矛盾,她生气了,于是不再通知这位同事,此时,她是否应该把这个对象从她加入的观察者列表中删除?”

“这个容易,调用‘Detach’方法将其减去就可以了。”

“好的,再去写写看。”

4 解耦实践二

又过了半小时后,小菜给出了第三版。

增加了抽象通知者接口

// 通知者接口
interface Subject
{
  void Attach(Observer observer);
  void Detach(Observer observer);
  void Notify();
  string SubjectState
  {
    get; set;
  }
}

具体的通知者类可能是前台,也可能是老板,它们也许有各自的一些方法,但对于通知者来说,它们是一样的,所以它们都去实现这个接口。

class Boss : Subject
{
  // 同事列表
  private IList<Observer> observers = new List<Observer>();
  private string action;

  //增加
  public void Attach(Observer observer)
  {
    observers.Add(observer);
  }

  //减少
  public void Detach(Observer observer)
  {
    observers.Remove(observer);
  }

  //通知
  public void Notify()
  {
    foreach (Observer o in observers)
    {
      o.Update();
    }
  }

  //老板状态
  public string SubjectState
  {
    get { return action; }
    set { action = value; }
  }
}

前台秘书类与老板类类似,略。

对于具体的观察者,需更改的地方就是把与‘前台’耦合的地方都改成针对抽象通知者。

/// <summary>
/// 抽象的观察者
/// </summary>
abstract class Observer
{
  protected string name;
  protected Subject sub;

  /// <summary>
  /// 通知
  /// </summary>
  /// <param name="name"></param>
  /// <param name="sub">原来是'前台',现改成抽象通知者</param>

  public Observer(string name, Subject sub)
  {
    this.name = name;
    this.sub = sub;
  }

  public abstract void Update();
}

/// <summary>
/// 看股票的同事
/// </summary>
class StockObserver : Observer
{
  /// <summary>
  /// 通知
  /// </summary>
  /// <param name="name"></param>
  /// <param name="sub">原来是'前台',现改成抽象通知者</param>
  public StockObserver(string name, Subject sub) : base(name, sub)
  {

  }

  public override void Update()
  {
    Console.WriteLine($"{sub.SubjectState} {name}关闭股票行情,继续工作! ");
  }
}

客户端代码如下:

static void Main(string[] args)
{
  // 老板胡汉三
  Boss huhansan = new Boss();

  // 看股票的同事
  StockObserver tongshi1 = new StockObserver("魏关姹", huhansan);
  // 看NBA的同事
  NBAObserver tongshi2 = new NBAObserver("易管查", huhansan);


  huhansan.Attach(tongshi1);
  huhansan.Attach(tongshi2);

  // 魏关姹其实是没有被老板通知到,所以减去
  huhansan.Detach(tongshi1);

  // 老板回来
  huhansan.SubjectState = "我胡汉三回来了!";

  // 发出通知
  huhansan.Notify();

  Console.Read();
}

运行结果

我胡汉三回来了! 易管查关闭NBA直播,继续工作!

“由于‘魏关姹’没有被通知到,所以他被当场‘抓获’,下场很惨。”小菜说道,“现在我做到了两者都不耦合了。”

“写得好,把结构图画出来看看。”

“这不难。”

小菜画出了代码的结构图。

老板回来,我不知道——观察者模式
结构图

“哈,小菜非常好,你已经把观察者模式的精华都写出来了,现在我们来看看什么叫观察者模式。”

5 观察者模式

观察者模式又叫做发布-订阅(Publish/Subscribe)模式。

观察者模式 定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。[DP]

老板回来,我不知道——观察者模式
观察者模式(Observer)结构图

Subject 类,可翻译为主题或抽象通知者,一般用一个抽象类或者一个接口实现。它把所有对观察者对象的引用保存在一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。

abstract class Subject
{
  private IList<Observer> observers = new List<Observer>();

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

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

  //通知
  public void Notify()
  {
    foreach (Observer o in observers)
    {
      o.Update();
    }
  }
}

Observer类,抽象观察者,为所有的具体观察者定义一个接口,在得到主题的通知时更新自己。这个接口叫做更新接口。抽象观察者一般用一个抽象类或者一个接口实现。更新接口通常包含一个Update()方法,这个方法叫做更新方法。

abstract class Observer
{
  public abstract void Update();
}

ConcreteSubject类,叫做具体主题或具体通知者,将有关状态存入具体现察者对象; ,在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个具体子类实现。

class ConcreteSubject : Subject
{
  /// <summary>
  /// 具体被观察者状态
  /// </summary>
  public string SubjectState { get; set; }
}

ConcreteObserver类,具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。具体观察者角色可以保存一个指向具体主题对象的引用。具体观察者角色通常用一个具体子类实现。

class ConcreteObserver : Observer
{
  private string name;
  private string observerState;
  private ConcreteSubject subject;

  public ConcreteObserver(ConcreteSubject subject, string name)
  {
    this.subject = subject;
    this.name = name;
  }

  public override void Update()
  {
    observerState = subject.SubjectState;
    Console.WriteLine($"观察者{name}的新状态是{observerState}");
  }

  public ConcreteSubject Subject
  {
    get { return subject; }
    set { subject = value; }
  }
}

客户端代码

static void Main(string[] args)
{
  ConcreteSubject s = new ConcreteSubject();

  s.Attach(new ConcreteObserver(s, "X"));
  s.Attach(new ConcreteObserver(s, "Y"));
  s.Attach(new ConcreteObserver(s, "Z"));

  s.SubjectState = "ABC";
  s.Notify();

  Console.Read();
}

结果显示

观察者X的新状态是ABC
观察者Y的新状态是ABC
观察者Z的新状态是ABC

6 观察者模式特点

“用观察者模式的动机是什么呢?”小菜问道。

“问得好,将一个系统分割成一系列相互协作的类有一个很不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便[DP]。而观察者模式的关键对象是主题Subject和观察者Observer,一个Subject可以有任意数目的依赖它的Observer,一旦Subject的状态发生了改变,所有的Observer都可以得到通知。Subject 发出通知时并不需要知道谁是它的观察者,也就是说,具体观察者是谁,它根本不需要知道。而任何一个具体观察者不知道也不需要知道其他观察者的存在。”

“什么时候考虑使用观察者模式呢?”

“你说什么时候应该使用?”大鸟反问道。

当一个对象的改变需要同时改变其他对象的时候。”

“补充一下,而且它不知道具体有多少对象有待改变时,应该考虑使用观察者模式。还有吗?”

“我感觉当一个抽象模型有两个方面,其中一方面依赖于另一方面,这时用观察者模式可以将这两者封装在独立的对象中使它们各自独立地改变和复用。”

“非常好,总的来讲,观察者模式所做的工作其实就是在解除耦合。让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响另一边的变化。”

“啊,这实在是依赖倒转原则的最佳体现呀。”小菜感慨道。

“我问你,在抽象观察者时,你的代码里用的是抽象类,为什么不用接口?”

“因为我觉得两个具体观察者,看股票观察者和看NBA观察者类是相似的,所以用了抽象类,这样可以共用一些代码,用接口只是方法上的实现,没什么太大意义。”

“那么抽象观察者可不可以用接口来定义?”

“用接口?我不知道,应该没必要吧。”

“哈,那是因为你不知道观察者模式的应用都是怎么样的。现实编程中,具体的观察者完全有可能是风马牛不相及的类,但它们都需要根据通知者的通知来做出 Update()的操作,所以让它们都实现下面这样的一个接口就可以实现这个想法了。”

interface Observer
{
  void Update();
}

“嘿,大鸟说得好,这时用接口比较好。”小菜傻笑道。突然他表情一变,“等等,这里还是有问题,这些控件要么是.NET 类库,要么是其他人事先写好的控件,它如何再能去实现拥有Update的 Observer接口呢?”

7 观察者模式的不足

大鸟微笑着说道:“举个例子让我听听。”

“这样的例子很好举呀,”小菜说,“比如,VS 2019,当你点击运行程序之前的时候,整个界面是下面这样的。”

老板回来,我不知道——观察者模式
未运行程序之前的VS 2019界面

“当运行程序以后,除了弹出一个控制台的程序窗体以外,工具栏发生了变化,工具箱不见了,‘错误列表’变成了‘自动窗口’和‘命令窗口’打开。仅仅是点击了一个‘运行’按钮,就发生了这么多的变化,而各个变化都涉及到不同的控件。”

老板回来,我不知道——观察者模式
运行程序之后的VS 2019界面

“我觉得没办法让每个控件都去实现一个‘Observer’的接口,因为这些控件都早已被它们的制造商给封装了。”

“小菜聪明,你问到点子上了,尽管当点击‘运行’按钮时,确实是在通知相关的控件产生变化,但它们是不可能用接口的方式来实现观察者模式的。”

“那怎么办呢?”

“是呀,那该怎么办呢?”

“凉拌呗。”大鸟得意之极。

“快说,摆什么架子!”

“还是回到刚才那个‘老板、前台与同事的例子’,你看看它还有什么不足之处?”

“和刚才说的问题一样呀,尽管已经用了依赖倒转原则,但是‘抽象通知者’还是依赖‘抽象观察者’,也就是说,万一没有了抽象观察者这样的接口,我这通知的功能就完不成了。另外就是每个具体观察者,它不一定是‘更新’的方法要调用呀,就像刚才说的,我希望的是‘工具箱’是隐藏,‘自动窗口’是打开,这根本就不是同名的方法。这应该就是不足的地方吧。”

“是呀,如果通知者和观察者之间根本就互相不知道,由客户端来决定通知谁,那就好了。来,我们先来把原来的代码改造一下。”

8 事件委托实现

“看股票观察者”类和“看NBA观察者”类,去掉了父类“抽象观察类”,所以补上一些代码,并将“更新”方法名改为各自适合的方法名。

/// <summary>
/// 看股票的同事
/// </summary>
class StockObserver
{
  private string name;
  private Subject sub;
  public StockObserver(string name, Subject sub)
  {
    this.name = name;
    this.sub = sub;
  }

  // 方法“更新”名改为“关闭股票程序”
  // 关闭股票行情
  public void CloseStockMarket()
  {
    Console.WriteLine($"{sub.SubjectState}{name}关闭股票行情,继续工作! ");

  }
}

/// <summary>
/// 看NBA的同事
/// </summary>
class NBAObserver 
{
  private string name;
  private Subject sub;
  public NBAObserver(string name, Subject sub)
  {
    this.name = name;
    this.sub = sub;
  }

  // 方法“更新”名改为“关闭NBA直播”
  // 关闭NBA直播
  public void CloseStockMarket()
  {
    Console.WriteLine($"{sub.SubjectState}{name}关闭NBA直播,继续工作! ");

  }
}

“现实中就是这样的,方法名本就不一定相同。”小菜点头说。

“抽象通知者”由于不希望依赖“抽象观察者”,所以“增加”和“减少”的方法也就没有必要了(抽象观察者已经不存在了)。

/// <summary>
/// 通知者接口
/// </summary>
interface Subject
{
  void Notify();

  string SubjectState { get; set; }
}

“下面就是如何处理‘老板’类和‘前台’类的问题,它们当中‘通知’方法有了对‘观察者’遍历,所以不可小视之。但如果在.NET中,我们可以用一个非常好的技术来处理这个问题,它叫……”

“快说。”小菜眼睛瞪大,盯着大鸟。

“它叫委托。”

“哦,就是委托呀,我都认真学过好几次了,但还是不太懂,同学中几个也都差不多,反正就不太明白,它到底是怎么回事,如何用。”

“先别管委托是怎么回事。我们看看如何做。”

声明一个委托,名称叫“EventHandler(事件处理程序)”,无参数,无返回值。

delegate void EventHandler();

“老板”类和“前台秘书”类

class Boss : Subject
{
  // 声明一事件Update,类型为委托EventHandler.
  // 声明一“EventHandler(事件处理程序)”的委托事件,名称叫“Update(更新)”
  public event EventHandler Update;

  private string action;

  public void Notify()
  {
    //  在访问“通知”方法时,调用“更新”
    Update();
  }

  public string SubjectState
  {
    get { return action; }
    set { action = value; }
  }
}

class Secretary : Subject
{
  // 与老板类类似,省略
}

客户端代码

static void Main(string[] args)
{
  //老板胡汉三
  Boss huhansan = new Boss();

  //看股票的同事
  StockObserver tongshi1 = new StockObserver("魏关姹", huhansan);

  //看NBA的同事
  NBAObserver tongshi2 = new NBAObserver("易管查", huhansan);

  // 将“看股票者”的“关闭股票程序”方法和“看NBA者”的“关闭NBA直播”方法挂钩到“老板”的“更新”上,也就是将两不同类的不同方法委托给“老板”类的“更新”了
  huhansan.Update += new EventHandler(tongshi1.CloseStockMarket);
  huhansan.Update += new EventHandler(tongshi2.CloseNBADirectSeeding);

  //老板回来
  huhansan.SubjectState = "我胡汉三回来了! ";

  //发出通知   
  huhansan.Notify();

  Console.Read();
}

显示结果

我胡汉三回来了! 魏关姹关闭股票行情,继续工作!
我胡汉三回来了! 易管查关闭NBA直播,继续工作!

9 事件委托说明

“现在可以来解释一下,委托是什么了。委托就是一种引用方法的类型。一旦为委托分配了方法,委托将与该方法具有完全相同的行为。委托方法的使用可以像其他任何方法一样,具有参数和返回值。委托可以看作是对函数的抽象,是函数的‘类’,委托的实例将代表一个具体的函数。”

“哦,你的意思是说,‘delegate void EventHandler();’,可以理解为声明了一个特殊的‘类’。而‘public event EventHandler Update;’可以理解为声明了一个‘类’的变量。”

“哈哈,应该是声明了一个事件委托变量叫‘更新’。”

“你说的委托的实例将代表一个具体的函数,意思是说,‘new EventHandler (tongshi1.CloseStockMarket)’其实就是一个委托的实例,而它就等于将‘tongshi1.CloseStockMarket’这个方法委托给‘huhansan.Update’这个方法了。”

“对了,就是这个意思,我刚才不是说过吗,一旦为委托分配了方法,委托将与该方法具有完全相同的行为。而且,一个委托可以搭载多个方法,所有方法被依次唤起。更重要的是,它可以使得委托对象所搭载的方法并不需要属于同一个类。你还不明白?”

“啊,我明白了,我明白了,”小菜脸笑开了花,“这样就使得,本来是在‘老板’类中的增加和减少的抽象观察者集合以及通知时遍历的抽象观察者都不必要了。转到客户端来让委托搭载多个方法,这就解决了本来与抽象观察者的耦合问题。”

“但委托也是有前提的,那就是委托对象所搭载的所有方法必须具有相同的原形和形式,也就是拥有相同的参数列表和返回值类型。”

“如果参数列表都不同那还瞎掺和啥。”小菜惊叹道,“太强了,当时那些牛人是怎么设计出这东西的,本来观察者模式已经把依赖倒转原则做到非常好了,现在看来,委托和事件,岂不是更加优秀,解决问题更加优雅?”

“注意,是先有观察者模式,再有委托事件技术的,再说,它们各有优缺点,你不妨去看看MSDN,讲得已经很详细了。”

“我现在对委托和事件有些了解了,相信再去研究MSDN也就不难了。这时再看看刚才举的那个例子,当点‘运行’时,所谓的‘工具箱隐藏’、‘错误列表隐藏’、‘自动窗口打开’、‘命令窗口打开’不过就是‘运行’时注册的四个事件的触发而已。别说就四个,就是四十个完全不同的控件,也都能通知到位了。”

“夸张是有点夸张,不过确实也是如此。”

(注:委托和事件在附录一中的1.13节中还有讲解,可参考)

10 石守吉失手机后的委托

突然小菜的手机响了。

“小菜,我是石守吉,昨天我手机丢了,没办法,只得重买一个。原来的那个号也没法办回来,还好我记得你的手机,所以用这个新号打给你了。你能不能把我们班级同学的号码抄一份发邮件给我?”

“哦,这个好办,不过班级这么多人,我要是抄起来,也容易错。而且,如果现在同学有急事要找你,不就找不到了吗?我们这样办吧……用观察者模式。”

“你说什么?我听不懂呀。什么观察者模式?”

“哈,其实就是我在这里给我们班级所有同学群发一条短消息,通知他们,你石守吉已换新号,请大家更新号码,有事可及时与石守吉联系。”

“好办法,你可记得一定要给李MM、张 MM、王 MM发哦。”

“你小子,首先想着的就是MM。放心吧,我才不管谁呢,凡是在我手机里存的班级同学,我都会循环遍历一遍,群发给他们的。”

“小菜怎么张口闭口都是术语呀,好的,你就循环遍历一下吧。这事就委托给你了,谢谢哦!”


下一篇我们接着读“第15章 就不能不换DB吗?——抽象工厂模式”,欢迎关注微信公众号【乐趣课堂】。

原文出处:微信公众号【乐趣课堂】

原文链接:https://mp.weixin.qq.com/s/RnWUnlsMdyAIyuOtkeoBig

本文观点不代表Dotnet9立场,转载请联系原作者。

发表评论

登录后才能评论