世界需要和平——中介者模式

世界需要和平——中介者模式

继上一篇“加薪非要老总批?——职责链模式”后,本文继续讲解《大话设计模式》第25章“世界需要和平——中介者模式”。喜欢本书请到各大商城购买原书,支持正版。

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

本文正式开始


1. 世界需要和平

时间:7月5日 20点 地点:小菜大鸟住所的客厅 人物:小菜、大鸟

“大鸟,今晚学习什么模式呢?”小菜问道。

“哦,今天讲中介者模式。”大鸟说。

“中介?就是那个房产或出国留学的中介?”

“哈,是的,就是那个词,中介者模式又叫做调停者模式。其实就是中间人或者调停者的意思。”

“举个例子来说说看。”

“比如最近伊拉克又接连发生了多起爆炸事件,战争带给人类的真是无法弥补的伤痛。”大鸟感慨道,“世界真的需要和平呀。”

“是呀,如果不是伊拉克战争,可能就没这么多事情了。相比较我们可真的太幸福了。”

“再比如巴以问题、伊朗核问题、朝鲜核问题以及各国间的政治外交问题,构成了极为复杂的国际形式。”

“这些和今天要讲的中介者模式有什么关系?”

“你想想看,由于各国之间代表的利益不同,所以矛盾冲突是难免的,但如果有这样一个组织,由各国的代表组成,用来维护国际和平与安全,解决国际间经济、社会、文化和人道主义性质的问题,不就很好吗?”

“啊,你指的就是联合国组织是吧,我明白了,它就是一个调停者、中介者的角色。”

“是的,各国之间关系复杂,战略盟友、战略伙伴、战略对手、利益相关者等等等等,各国政府都需要投入大量的人力物力在政治、经济、外交方面来搞好这些关系,但不管如何努力,国与国之间的关系还是会随着时间和社会发展而发生改变。在第二次世界大战以前,由于没有这样一个民主中立的协调组织,使得就出现了法西斯联盟,给人类史上造成最大的灾难——二战。而自1945年成立了联合国之后,地球上再没有发生世界范围的战争,可以说,联合国对世界和平的贡献不可估量。”

“啊,原来没有大型战争的原因在这里。那这和我们的软件设计模式又有什么关系呢?”

“你想呀,国与国之间的关系,就类似于不同的对象与对象之间的关系,这就要求对象之间需要知道其他所有对象,尽管将一个系统分割成许多对象通常可以增加其可复用性,但是对象间相互连接的激增又会降低其可复用性了。你知道为什么会这样?”

“我想是因为大量的连接使得一个对象不可能在没有其他对象的支持下工作,系统表现为一个不可分割的整体,所以,对系统的行为进行任何较大的改动就十分困难了。”

世界需要和平——中介者模式

“总结得很好嘛,要解决这样的问题,可以应用什么原则?”

“我记得之前讲过的叫‘迪米特法则’,如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。在这里,你的意思就是说,国与国之间完全可以通过‘联合国’这个中介者来发生关系,而不用直接通信。”

“是呀,通过中介者对象,可以将系统的网状结构变成以中介者为中心的星形结构,每个具体对象不再通过直接的联系与另一个对象发生相互作用,而是通过‘中介者’对象与另一个对象发生相互作用。中介者对象的设计,使得系统的结构不会因为新对象的引入造成大量的修改工作。”

世界需要和平——中介者模式

“当时你对我解释‘迪米特法则’的时候,是以我们公司的IT部门的管理为例子的,其实让我一个刚进公司的人去求任何一个不认识的IT部同事帮忙是有困难的,但如果是有IT主管来协调工作,就不至于发生我第一天上班却没有电脑进行工作的局面。IT主管就是一个‘中介者’对象了。”

2 中介者模式

“说得好,看来这个模式很容易理解嘛!来,我们看看这个模式的定义。”

中介者模式(Mediator),用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。[DP]

中介者模式(Mediator)结构图

世界需要和平——中介者模式

“Colleague叫做抽象同事类,而ConcreteColleague是具体同事类,每个具体同事只知道自己的行为,而不了解其他同事类的情况,但它们却都认识中介者对象,Mediator是抽象中介者,定义了同事对象到中介者对象的接口,ConcreteMediator是具体中介者对象,实现抽象类的方法,它需要知道所有具体同事类,并从具体同事接收消息,向具体同事对象发出命令。”

Mediator类抽象中介者类

abstract class Mediator
{
  // 定义一个抽象的发送消息方法,得到同事对象和发送信息
  public abstract void Send(string message, Colleague colleague);
}

Colleague类 抽象同事类

abstract class Colleague
{
  protected Mediator mediator;
  public Colleague(Mediator mediator)
  {
    // 构造方法,得到中介者对象
    this.mediator = mediator;
  }
}

ConcreteMediator类具体中介者类

class ConcreteMediator : Mediator
{
  // 需要了解所有的具体同事对象
  private ConcreteColleague1 colleaguel;
  private ConcreteColleague2 colleague2;
  
  public ConcreteColleague1 Colleague1
  {
    set { colleague1 = value; }
  }
  
  public ConcreteColleague2 colleague2
  {
    set { colleague2 = value; }
  }
  
  // 重写发送信息的方法,根据对象做出选择判断,通知对象
  public override void Send(string message, Colleague colleague)
  {
    if (colleague == colleague1)
    {
      colleague2.Notify(message);
    }
    else
    {
      colleague1.Notify(message);
    }
  }
}

ConcreteColleague1和 ConcreteColleague2等各种同事对象

class ConcreteColleaguel : Colleague
{
  public ConcreteColleague1(Mediator mediator)
    : base (mediator)
  {
  
  }
  
  // 发送信息时通常是中介者发送出去的
  public void Send(string message)
  {
    mediator.Send(message, this);
  }

  public void Notify(string message)
  {
    Console.WriteLine("同事1得到信息: " + message);
  }
}

class ConcreteColleague2 : Colleague
{
  public ConcreteColleague2(Mediator mediator) : base(mediator){
  )
  
  public void Send(string message)
  {
    mediator.Send(message, this);
  }
  
  public void Notify(string message)
  {
    Console.WriteLine("同事2得到信息:" + message);
  }
}

客户端调用

static void Main(string[] args)
{
  ConcreteMediator m = new ConcreteMediator();
  
  // 让两个具体同事类认识中介者对象
  ConcreteColleague1 c1 = new ConcreteColleaguel(m);
  ConcreteColleague2 c2 = new ConcreteColleague2(m);
  
  // 让中介者认识各个具体同事类对象
  m.Colleague1 = c1;
  m.Colleague2 = c2;
  
  // 具体同事类对象的发送信息都是通过中介者转发
  c1.Send("吃过饭了吗?");
  c2.Send("没有呢,你打算请客?");
  
  Console.Read();
}

“由于有了Mediator,使得ConcreteColleaguel和ConcreteColleague2在发送消息和接收信息时其实是通过中介者来完成的,这就减少了它们之间的耦合度了。”大鸟说,“好了,该你来练习了,需求是美国和伊拉克之间的对话都是通过联合国安理会作为中介来完成。”

“哦,这应该没什么大问题,美国和伊拉克都是国家,有一个国家抽象类和两个具体国家类就可以了。但‘联合国’到底是Mediator还是ConcreteMediator 呢?”

“这要取决于未来是否有可能扩展中介者对象,比如你觉得联合国除了安理会,还有没有可能有其他机构存在呢?”

“哈,大鸟你启发我了,联合国的机构还有如国际劳工组织、教科文组织、世界卫生组织、世界贸易组织等等,很多的,所以Mediator应该是‘联合国机构’,而‘安理会’是一个具体的中介者。”

“很好,如果不存在扩展情况,那么Mediator可以与ConcreteMediator合二为一。”大鸟说,“开始吧。”

3 安理会做中介

过了近一个小时,小菜才将代码写了出来。

代码结构图

世界需要和平——中介者模式

联合国机构类相当于Mediator类/联合国机构

abstract class UnitedNations
{
  // 声明
  public abstract void Declare(string message, Country colleague);
}

国家类相当于Colleague类

// 国家
abstract class Country
{
  protected UnitedNations mediator;
  public Country(UnitedNations mediator)
  {
    this.mediator = mediator;
  }
}

美国类相当于ConcreteColleague1类

//美国
class USA : Country
{
  public USA(UnitedNations mediator) : base (mediator)
  {}
  
  //声明
  public void Declare(string message)
  {
    mediator.Declare(message, this);
  }
  
  //获得消息
  public void GetMessage(string message)
  {
    Console.WriteLine("美国获得对方信息:" + message);
  }
}

伊拉克类相当于ConcreteColleague2类

// 伊拉克
class Iraq : Country
{
  public Iraq(UnitedNations mediator) : base (mediator)
  {}
  
  //声明
  public void Declare(string message)
  {
    mediator.Declare(message, this);
  }
  
  //获得消息
  public void GetMessage(string message)
  {
    Console.WriteLine("伊拉克获得对方信息:" + message);
  }
}

联合国安理会相当于ConcreteMediator类

//联合国安全理事会
class UnitedNationsSecurityCouncil : UnitedNations
{
  private USA colleaguel;
  private Iraq colleague2;
  
  联合国安理会了解所有的国家,所以拥有美国和伊拉克的对象属性
  
  //美国  
  public USA Colleague1
  { 
    set { colleague1 = value; }
  }
  
  //伊拉克
  public Iraq Colleague2
  { set { colleague2 = value; } }
  
  //声明  重写了“声明”方法,实现了两个对象间的通信
  public override void Declare(string message,Country colleague)
  {
  if (colleague == colleague1)
  {
    colleague2.GetMessage(message);
  }  
  else
  {
    colleague1.GetMessage(message
  }
}

客户端调用

static void Main (string [] args)
{
  UnitedNationsSecurityCouncil UNSC = new UnitedNationsSecurityCouncil();
  USA cl = new USA(UNSC);
  Iraq c2 = new Iraq(UNSC);
  UNSC.Colleague1 = c1;
  UNSC.Colleague2 = c2;
  c1.Declare("不准研制核武器,否则要发动战争!");
  c2.Declare("我们没有核武器,也不怕侵略。");
  Console.Read();
}

结果显示

伊拉克获得对方信息:不准研制核武器,否则要发动战争。
美国获得对方信息:我们没有核武器,也不怕侵略。

“小菜呀,你这样的写法和我写的样例代码有什么差别呀,除了类名变量名的不同,没什么区别。这样的代码为何还要写这么长的时间呢?”

“哈,我边写边在思考它是如何做到中介的,其实最关键的问题在于ConcreteMediator这个类必须要知道所有的ConcreteColleague,这好像有些问题?”

“你的想法是什么呢?”

“我觉得尽管这样的设计可以减少了ConcreteColleague类之间的耦合,但这又使得ConcreteMediator责任太多了,如果它出了问题,则整个系统都会有问题了。”

4 中介者模式优缺点

“说得好,如果联合国安理会出了问题,当然会对世界都造成影响。所以说,中介者模式很容易在系统中应用,也很容易在系统中误用。当系统出现了‘多对多’交互复杂的对象群时,不要急于使用中介者模式,而要先反思你的系统在设计上是不是合理。你来总结一下中介者模式的优缺点吧。”

“我觉得中介者模式的优点首先是Mediator的出现减少了各个Colleague的耦合,使得可以独立地改变和复用各个Colleague类和Mediator,比如任何国家的改变不会影响到其他国家,而只是与安理会发生变化。其次,由于把对象如何协作进行了抽象,将中介作为一个独立的概念并将其封装在一个对象中,这样关注的对象就从对象各自本身的行为转移到它们之间的交互上来,也就是站在一个更宏观的角度去看待系统。比如巴以冲突,本来只能算是国与国之间的矛盾,因此各自的看法可能都比较狭隘,但站在联合国安理会的角度,就可以从全球化、也更客观角度来看待这个问题,在调停和维和上做出贡献。”

“哇,小菜不简单,一个小时的编码,原来你想到这么多,不容易不容易,你说得非常好,用中介者模式的确可以从更宏观的角度来看待问题。那中介者模式的缺点呢?”

“应该就是你刚才提到的,具体中介者类ConcreteMediator可能会因为ConcreteColleague的越来越多,而变得非常复杂,反而不容易维护了。”

“是的,由于ConcreteMediator控制了集中化,于是就把交互复杂性变为了中介者的复杂性,这就使得中介者会变得比任何一个 ConcreteColleague 都复杂。事实上,联合国安理会秘书长的工作应该是非常繁忙的,谁叫他就是‘全球最大的官’呢。也正因为此,中介者模式的优点来自集中控制,其缺点也是它,使用时是要考虑清楚哦。”

“啊,这么讲,我感觉中介者模式的应用很少的。”

“小菜,实际上你一直在用它而不自知呀。”大鸟得意地说道。

“哦,有吗?是什么时候?”小菜疑惑着。

“你平时用.NET 写的 Windows应用程序中的Form或Web网站程序的aspx就是典型的中介者呀。”

“啊,不会吧。这是怎么讲呢?”

“比如计算器程序,它上面有菜单控件、文本控件、多个按钮控件和一个Form窗体,每个控件之间的通信都是通过谁来完成的?它们之间是否知道对方的存在?”

世界需要和平——中介者模式

“哦,我知道了,每个控件的类代码都被封装了,所以它们的实例是不会知道其他控件对象的存在的,比如点击数字按钮要在文本框中显示数字,按照我以前的想法就应该要在Button类中编写给TextBox类实例的Text 属性赋值的代码,造成两个类有耦合,这显然是非常不合理的。但实际情况是它们都有事件机制,而事件的执行都是在Form窗体的代码中完成,也就是说所有的控件的交互都是由Form窗体来作中介,操作各个对象,这的确是典型的中介者模式应用。”

“是的,说得很好,”大鸟鼓励道,“中介者模式一般应用于一组对象以定义良好但是复杂的方式进行通信的场合,比如刚才得到的窗体Form对象或Web页面 aspx,以及想定制一个分布在多个类中的行为,而又不想生成太多的子类的场合。”

“明白,回到联合国的例子,我相信如果所有的国际安全问题都上升到安理会来解决,世界将不再有战争,世界将会永远和平。”

“让世界充满爱,世界呼唤和平。”


每天学一点,不贪多。

下一篇我们接着读“第26章 项目多也别傻做——享元模式”,欢迎关注微信公众号【乐趣课堂】。

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

原文链接:

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

发表评论

登录后才能评论