• 设计模式的征途—22.中介者(Mediator)模式


    我们都用过QQ,它有两种聊天方式:一是私聊,二是群聊。使用QQ群,一个用户就可以向多个用户发送相同的信息和文件,从而无需一一发送,节省大量时间。通过引入群的机制,极大地减少系统中用户之间的两两通信,用户与用户之间的联系可以通过群的机制来实现。

    在有些软件中,某些类/对象之间的相互调用关系错综复杂,类似于QQ用户之间的关系,此时,特别需要一个类似“QQ群”一样的中间类来协调这些类/对象之间的复杂关系,以降低系统的耦合度。因此,一个设计模式因此诞生,它就是中介者模式。

    中介者模式(Mediator) 学习难度:★★★☆☆ 使用频率:★★☆☆☆

    一、客户信息管理模块的初始设计

    1.1 需求背景

    Background:M公司欲开发一套CRM系统,其中包含一个客户信息管理模块,所涉及的“客户信息管理窗口”界面效果图如下图所示:

    M公司开发人员通过分析发现,在上图中,界面组件之间存在较为复杂的交互关系:如果删除一个客户,则将从客户列表中删掉对应的项,客户选择组合框中客户名称也称将减少一个;如果增加一个客户信息,则客户列表中将增加一个客户,且组合框中也将增加一项。  

    1.2 初始设计

      M公司开发人员针对组件之间的交互关系进行了分析,发现:

      (1)当用户单击“增加”、“删除”、“修改”或“查询”时,界面左侧的“客户选择组合框”、“客户列表”以及界面中的文本框将产生响应。

      (2)当用户通过”客户选择组合框“选中某个客户姓名时,”客户列表“和文本框将产生响应。

      (3)当用户通过“客户列表”选中某个客户姓名时,“客户选择组合框”和文本框将产生响应。

      根据交互关系,开发人员绘制了如下图所示的初始类图。

      不难发现,上述设计存在以下问题:

      (1)系统结构负责且耦合度高 => 复杂的网状结构,OMG!

      (2)组件的可重用性差 =>  由于每一个组件和其他组件之间都具有很强的关联,很难重用!

      (3)系统的扩展性差 => 如果在上述系统中增加一个新的组件类,必须修改与之交互的各个组件源代码!

    二、中介者模式概述

    2.1 中介者模式简介

      如果在一个系统中对象之间存在多对多的相互关系,可以将对象之间的一些交互行为从各个对象中分离出来,并集中封装在一个中介者对象中,并由该中介者进行统一协调,这样对象之间多对多的复杂关系就转化为了相对简单的一对多关系。通过引入中介者来简化对象之间的复杂关系,它是迪米特法则的一个典型应用。

    中介者(Mediator)模式:用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显示地相互引用,从而使其耦合松散,而且可以相对独立地改变它们之间的交互。中介者模式又称为调停模式,它是一种对象行为型模式。  

    2.2 中介者模式结构

      在中介者模式中,引入了用于协调其他对象/类之间的相互调用的中介者类,为了让系统具有更好的灵活性和可扩展性,通常还提供了抽象中介者,其结构图如下图所示:

      中介者模式结构图中主要包含以下4个角色:

      (1)Mediator(抽象中介者):它定义了一个接口,该接口用于与各同事对象之间进行通信。

      (2)ConcreteMediator(具体中介者):它实现了接口,通过协调各个同事对象来实现协作行为,维持了各个同事对象的引用。

      (3)Colleague(抽象同事类):它定义了各个同事类公有的方法,并声明了一些抽象方法来供子类实现,同时维持了一个对抽象中介者类的引用,其子类可以通过该引用来与中介者通信。

      (4)ConcreteColleague(具体同事类):抽象同事类的子类,每一个同事对象需要和其他对象通信时,都需要先与中介者对象通信,通过中介者来间接完成与其他同事类的通信。

    三、客户信息管理模块的重构实现

    3.1 重构后的设计结构

      M公司开发人员借助中介者模式来重新设计结构如下图所示:

      在具体实现时,为了确保系统有更好的灵活性和可扩展性,需要定义抽象中介者和抽象组件类,其中抽象组件类是所有具体组件类的公共父类,完整类图如下图所示:

      其中,Component充当抽象同事类,Button,List,ComboBox和TextBox充当具体同事类,Mediator充当抽象中介者类,ConcreteMediator充当具体中介类。在ConcreteMediator中维持了对具体同事对象的引用,为了简化ConcreteMediator类的代码,在其中只定义了一个Button对象和TextBox对象。

    3.2 重构后的代码实现

      (1)抽象中介者:Mediator

        /// <summary>
        /// 抽象中介者
        /// </summary>
        public abstract class Mediator
        {
            public abstract void ComponenetChanged(Component c);
        }

      (2)具体中介者:ConcreteMediator

        /// <summary>
        /// 具体中介者
        /// </summary>
        public class ConcreteMediator : Mediator
        {
            // 维持对各个同事对象的引用
            public Button addButton;
            public List list;
            public TextBox userNameTextBox;
            public ComboBox cb;
    
            // 封装同事对象之间的交互
            public override void ComponenetChanged(Component c)
            {
                // 单击按钮
                if (c == addButton)
                {
                    Console.WriteLine("-- 单击增加按钮 --");
                    list.Update();
                    cb.Update();
                    userNameTextBox.Update();
                }
                // 从列表框选择客户
                else if (c == list)
                {
                    Console.WriteLine("-- 从列表框选择客户 --");
                    cb.Select();
                    userNameTextBox.SetText();
                }
                // 从组合框选择客户
                else if (c == cb)
                {
                    Console.WriteLine("-- 从组合框选择客户 --");
                    cb.Select();
                    userNameTextBox.SetText();
                }
            }
        }

      (3)抽象同事类:Colleague

        /// <summary>
        /// 抽象同事类:抽象组件
        /// </summary>
        public abstract class Component
        {
            protected Mediator mediator;
    
            public void SetMediator(Mediator mediator)
            {
                this.mediator = mediator;
            }
    
            // 转发调用
            public void Changed()
            {
                mediator.ComponenetChanged(this);
            }
    
            public abstract void Update();
        }

      (4)具体同事类:ConcreteColleague

        /// <summary>
        /// 具体同事类:按钮组件
        /// </summary>
        public class Button : Component
        {
            public override void Update()
            {
                // 按钮不产生响应
            }
        }
    
        /// <summary>
        /// 具体同事类:列表框组件
        /// </summary>
        public class List : Component
        {
            public override void Update()
            {
                Console.WriteLine("列表框增加一项:张无忌");
            }
    
            public void Select()
            {
                Console.WriteLine("列表框选中项:小龙女");
            }
        }
    
        /// <summary>
        /// 具体同事类:组合框组件
        /// </summary>
        public class ComboBox : Component
        {
            public override void Update()
            {
                Console.WriteLine("组合框增加一项:张无忌");
            }
    
            public void Select()
            {
                Console.WriteLine("组合框选中项:小龙女");
            }
        }
    
        /// <summary>
        /// 具体同事类:文本框组件
        /// </summary>
        public class TextBox : Component
        {
            public override void Update()
            {
                Console.WriteLine("客户信息增加成功后文本框清空");
            }
    
            public void SetText()
            {
                Console.WriteLine("文本框显示:小龙女");
            }
        }
    
        /// <summary>
        /// 具体同事类:标签组件
        /// </summary>
        public class Label : Component
        {
            public override void Update()
            {
                Console.WriteLine("文本标签内容改变,客户信息总数量加1");
            }
        }

      (5)客户端测试:

        public class Program
        {
            public static void Main()
            {
                // Step1.定义中介者对象
                ConcreteMediator mediator = new ConcreteMediator();
    
                // Step2.定义同事对象
                Button addButton = new Button();
                List list = new List();
                ComboBox cb = new ComboBox();
                TextBox userNameTextBox = new TextBox();
    
                addButton.SetMediator(mediator);
                list.SetMediator(mediator);
                cb.SetMediator(mediator);
                userNameTextBox.SetMediator(mediator);
    
                mediator.addButton = addButton;
                mediator.list = list;
                mediator.cb = cb;
                mediator.userNameTextBox = userNameTextBox;
    
                // Step3.点击增加按钮
                addButton.Changed();
    
                Console.WriteLine("---------------------------------------------");
    
                // Step4.从列表框选择客户
                list.Changed();
            }
        }

      调试运行后的结果如下所示:

      

    四、中介者模式小结

    4.1 主要优点

      (1)简化了对象之间的交互,它用中介者和同事的一对多交互替代了原来同事之间的多对多交互

      (2)将各同事对象解耦,可以独立地改变和复用每个同事和中介者,增加新的中介和同事很方便,符合开闭原则。

      (3)可以减少大量同事子类的生成,改变同事行为只需要生成新的中介者子类即可。

    4.2 主要缺点

      具体中介者子类中包含了大量的同事之间的交互细节,可能会导致具体中介者类非常复杂,使得系统难以维护。

    4.3 应用场景

      (1)系统中对象之间存在复杂的引用关系 => 系统结构混乱且难以理解

      (2)一个对象由于引用了其他很多对象并且直接和这些对象通信 => 难以复用该对象

      (3)想要通过一个中间类来封装多个类的行为又不想生成太多子类 => 引入中介者即可实现

    参考资料

      DesignPattern

      (1)刘伟,《设计模式的艺术—软件开发人员内功修炼之道》

  • 相关阅读:
    微软教程:ASP.NET Core MVC 入门
    微软教程:ASP.NET Core Razor Pages 入门
    微软教程:ASP.NET Core Web API 入门
    微软教程:ASP.NET Core SignalR 入门(实时Web应用)
    Entity Framework Core 系列教程(翻译)
    视频教程:ASP.NET Core 3.x 构建 RESTful API(高级)
    C#(99):C# 9.0 新特性( NET Framework 5.0 与 Visual Studio ? )
    视频教程:VS Core 40分钟进行WebAPI开发和调用(入门级别)
    视频教程:ASP.NET Core 3.x 入门(包括MVC、Razor Page、Blazor、SignalR、gRPC)
    EntityFramework Core入门教程-12-在ASP.NET Core项目中配置EF Core
  • 原文地址:https://www.cnblogs.com/edisonchou/p/7497234.html
Copyright © 2020-2023  润新知