中介者模式(Mediator)

使用中介者模式来解决问题

定义

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

中介者模式的结构和说明

  • Mediator: 中介者接口。在里面定义各个同事之间交互需要的方法,可以是公共的通信方法,比如 changed 方法,大家都用,也可以是小范围的交互方法。
  • ConcreteMediator:具体中介者实现对象。它需要了解并维护各个同事对象,并负责具体的协调各同事对象的交互关系。
  • Colleague: 同事类的定义,通常实现成为抽象类,主要负责约束同事对象的类型并实现一些具体同事类之间的公共功能,比如,每个具体同事类都应该知道中介者对象,也就是具体同事类都会持有中介者对象,都可以定义到这个类里面。
  • ConcreteColleague:具体的同事类,实现自己的业务,在需要与其他同事通信的时候,就与持有的中介者通信,中介者会负责与其他的同事交互。

实例代码

namespace NetCore3Console.Mediator
{
    /// <summary>
    /// 同事类的抽象父类
    /// </summary>
    public abstract class Colleague
    {
        //持有中介者对象,每一个同事类都知道他的中介者对象
        private IMediator _mediator;

        protected Colleague(IMediator mediator)
        {
            _mediator = mediator;
        }

        /// <summary>
        /// 获取当前同事类对应的中介者对象
        /// </summary>
        /// <returns></returns>
        public IMediator GetMediator()
        {
            return _mediator;
        }
    }

    /// <summary>
    /// 具体的同事类A
    /// </summary>
    public class ConcreteColleagueA : Colleague
    {
        public ConcreteColleagueA(IMediator mediator) : base(mediator)
        {
        }
        /// <summary>
        /// 示意方法,执行某些业务功能
        /// </summary>
        public void SomaOperation()
        {
            //在需要跟其他同事通信的时候,通知中介者对象
            GetMediator().Changed(this);
        }
    }

    /// <summary>
    /// 具体的同事类B
    /// </summary>
    public class ConcreteColleagueB : Colleague
    {
        public ConcreteColleagueB(IMediator mediator) : base(mediator)
        {
        }
        /// <summary>
        /// 示意方法,执行某些业务功能
        /// </summary>
        public void SomaOperation()
        {
            //在需要跟其他同事通信的时候,通知中介者对象
            GetMediator().Changed(this);
        }
    }


    /// <summary>
    /// 中介者,定义各个同事对象通信的接口
    /// </summary>
    public interface IMediator
    {
        /// <summary>
        /// 同事对象在自身改变的时候来通知中介者的方法
        /// 让中介者去负责相应的与其他同事对象的交互
        /// </summary>
        /// <param name="colleague">同事对象自身,好让中介者对象通过对象实例去获取同事对象的状态</param>
        void Changed(Colleague colleague);
    }

    /// <summary>
    /// 具体的中介者实现
    /// </summary>
    public class ConcreteMediator : IMediator
    {
        //持有并维护同事A
        private ConcreteColleagueA colleagueA;
        //持有并维护同事B
        private ConcreteColleagueB colleagueB;

        /// <summary>
        /// 设置中介者需要了解并维护的同事A对象
        /// </summary>
        /// <param name="colleague"></param>
        public void SetConcreteColleagueA(ConcreteColleagueA colleague)
        {
            colleagueA = colleague;
        }

        /// <summary>
        /// 设置中介者需要了解并维护的同事B对象
        /// </summary>
        /// <param name="colleague"></param>
        public void SetConcreteColleagueB(ConcreteColleagueB colleague)
        {
            colleagueB = colleague;
        }

        public void Changed(Colleague colleague)
        {
            //某个同事类发生了变化,通常需要与其他同事交互
            //具体协调相应的同事对象来实现协作行为
        }
    }
}

使用中介者模式实现示例

要使用中介者模式来实现示例,那就要区分出同事对象和中介者对象。很明显,主板是作为中介者,而光驱、CPU、声卡、显卡等配件,都是作为同事对象。

namespace NetPlay.DesignPatten.Mediator;

/// <summary>
/// 所有同事的抽象父类的定义
/// </summary>
public abstract class Colleague
{
    private readonly IMediator _mediator;

    protected Colleague(IMediator mediator)
    {
        _mediator = mediator;
    }

    public IMediator GetMediator()
    {
        return _mediator;
    }
}

/// <summary>
/// 光驱类,一个同事类
/// </summary>
public class CDDriver : Colleague
{
    public CDDriver(IMediator mediator) : base(mediator)
    {
    }

    //光驱读取出来的数据
    private string data = "";
    //获取光驱读取出来的数据
    public string GetData()
    {
        return data;
    }
    /// <summary>
    /// 读取光盘(业务方法,也是和中介者交互的方法)
    /// </summary>
    public void ReadCd()
    {
        data = "设计模式,值得好好研究";
        //通知主板,自己的状态发生了改变
        GetMediator().Changed(this);
    }
}

/// <summary>
/// CPU类,一个同事类
/// </summary>
public class CPU : Colleague
{
    public CPU(IMediator mediator) : base(mediator)
    {
    }
    //分解出来的视频数据
    private string videoData = "";
    //分解出来的声音数据
    private string soundData = "";

    public string GetVideoData()
    {
        return videoData;
    }

    public string GetSoundData()
    {
        return soundData;
    }

    /// <summary>
    /// 处理数据,把数据分成音频和视频的数据
    /// </summary>
    /// <param name="data"></param>
    public void ExecuteData(string data)
    {
        var ss = data.Split(",");
        videoData = ss[0];
        soundData = ss[1];
        //通知主板,cpu的工作完成
        GetMediator().Changed(this);
    }
}

/// <summary>
/// 显卡类,一个同事类
/// </summary>
public class VideoCard : Colleague
{
    public VideoCard(IMediator mediator) : base(mediator)
    {
    }

    /// <summary>
    /// 显示视频数据
    /// </summary>
    /// <param name="data"></param>
    public void ShowData(string data)
    {
        Console.WriteLine("您正观看的是:" + data);
    }
}

/// <summary>
/// 声卡类,一个同事类
/// </summary>
public class SoundCard : Colleague
{
    public SoundCard(IMediator mediator) : base(mediator)
    {
    }

    public void SoundData(string data)
    {
        Console.WriteLine("画外音:" + data);
    }
}


/// <summary>
/// 中介者对象的接口
/// </summary>
public interface IMediator
{
    /// <summary>
    /// 同事对象在自身改变的时候来通知中介者的方法
    /// 让中介者去负责相应的与其他同事对象的交互
    /// </summary>
    /// <param name="colleague">同事对象自身,好让中介者对象通过对象实例去获取同事对象的状态</param>
    void Changed(Colleague colleague);
}

/// <summary>
/// 主板类,实现终结者接口
/// </summary>
public class MotherBoard : IMediator
{
    //需要知道要交互的同事类-光驱类
    private CDDriver _cdDriver = null;
    private CPU _cpu = null;
    private VideoCard _videoCard = null;
    private SoundCard _soundCard = null;

    public void SetCdDriver(CDDriver cdDriver)
    {
        _cdDriver = cdDriver;
    }

    public void SetCpu(CPU cpu)
    {
        _cpu = cpu;
    }

    public void SetVideoCard(VideoCard videoCard)
    {
        _videoCard=videoCard;
    }

    public void SetSoundCard(SoundCard soundCard)
    {
        _soundCard = soundCard;
    }


    public void Changed(Colleague colleague)
    {
        if (colleague == _cdDriver)
        {
            OpeCDDriverReadData((CDDriver)colleague);
        }
        else if (colleague == _cpu)
        {
            OpeCpu((CPU)colleague);
        }
    }

    /// <summary>
    /// 处理光驱读取数据以后与其他对象的交互
    /// </summary>
    /// <param name="cd"></param>
    private void OpeCDDriverReadData(CDDriver cd)
    {
        var data = cd.GetData();
        _cpu.ExecuteData(data);
    }

    private void OpeCpu(CPU cpu)
    {
        //先获取cpu处理后的数据
        var videoData = cpu.GetVideoData();
        var soundData = cpu.GetSoundData();
        //把这些数据传递给声卡和显卡
        _videoCard.ShowData(videoData);
        _soundCard.SoundData(soundData);
    }
}

认识中介者模式

中者式的能

中介者的功能非常简单,就是封装对象之间的交互。如果一个对象的操作会引起其他相关对象的变化,或者是某个操作需要引起其他对象的后续或连带操作,而这个对象又不希望自己来处理这些关系,那么就可以找中介者,把所有的麻烦扔给它,只在需要的时候通知中介者,其他的就让中介者去处理就可以了。

反过来,其他的对象在操作的时候,可能会引起这个对象的变化,也可以这么做。最后对象之间就完全分离了,谁都不直接跟其他对象交互,那么相互的关系全部被集中到中介者对象里面了,所有的对象就只是跟中介者对象进行通信,相互之间不再有联系。

把所有对象之间的交互都封装在中介者当中,无形中还可以得到另外一个好处,就是能够集中地控制这些对象的交瓦关系,这样当有变化的时候,修改起来就很方便。

需要 Mediator 接口吗

有没有使用 Mediator 接口的必要,那就取决于是否会提供多个不同的中介者实现。如果中介者实现只有一个的话,而且预计中也没有需要扩展的要求,那么就可以不定义 Mediator 接口,让各个同事对象直接使用中介者实现对象;如果中介者实现不只一个,或者预计中有扩展的要求,那么就需要定义Mediator 接口,让各个同事对象来面向中介者接口编程,而无须关心具体的中介者实现。

如何实现同事和中介者的通信

一种实现方式是在 Mediator 接口中定义一个特殊的通知接口,作为一个通用的方法让各个同事类来调用这个方法,在中介者模式结构图里画的就是这种方式。

另外一种实现方式是可以采用观察者模式,把 Mediator 实现成为观察者,而各个同事类实现成为 Subject,这样同事类发生了改变,会通知 Mediator。Mediator 在接到通知以后,会与相应的同事对象进行交互。

广义中介者

在实际应用开发中,经常会简化中介者模式,来使开发变得简单比如有如下的简化:

  • 通常会去掉同事对象的父类,这样可以让任意的对象,只要需要相互交互,就可
    以成为同事。
  • 通常不定义 Mediator 接口,把具体的中介者对象实现成为单例。
  • 同事对象不再持有中介者,而是在需要的时候直接获取中介者对象并调用;中介者也不再持有同事对象,而是在具体处理方法里面去创建,或者获取,或者从参数传入需要的同事对象。

demo

using System.Globalization;

namespace NetPlay.DesignPatten.Mediator;

/// <summary>
/// 部门类
/// </summary>
public class Dep
{
    /// <summary>
    /// 描述部门编号
    /// </summary>
    public string DepId { get; set; }
    /// <summary>
    /// 部门名称
    /// </summary>
    public string DepName { get; set; }

    /// <summary>
    /// 撤销部门
    /// </summary>
    /// <returns></returns>
    public bool DeleteDep()
    {
        //要先通过中介者去除掉所有与这个部门相关的部门和人员的关系
        var mediator = DepUserMediatorImpl.GetInstance();
        mediator.DeleteDep(DepId);
        //2:然后才能真正地清除掉这个部门
        //请注意在实际开发中,这些业务功能可能会做到业务层去
        //而且实际开发中对于已经使用的业务数据通常是不会被删除的
        //而是会被作为历史数据保留
        return true;
    }
}

/// <summary>
/// 人员类
/// </summary>
public class User
{
    public string UserName { get; set; }
    public string UserId { get; set; }

    /// <summary>
    /// 人员离职
    /// </summary>
    /// <returns></returns>
    public bool Dimission()
    {
        var mediator = DepUserMediatorImpl.GetInstance();
        mediator.DeleteUser(UserId);

        return true;
    }
}

/// <summary>
/// 部门和人员关系的类
/// </summary>
public class DepUserModel
{
    /// <summary>
    /// 主键
    /// </summary>
    public string DepUserId { get; set; }
    public string DepId { get; set; }
    public string UserId { get; set; }
}

/// <summary>
/// 具体的中介者实现
/// 只示例实现撤销部门和人员离职的功能
/// </summary>
public class DepUserMediatorImpl
{
    private static DepUserMediatorImpl _mediator=new DepUserMediatorImpl();

    private DepUserMediatorImpl()
    {
        //调用测试数据
        //InitTestData()''
    }

    /// <summary>
    /// 实现成单例
    /// </summary>
    /// <returns></returns>
    public static DepUserMediatorImpl GetInstance()
    {
        return _mediator;
    }

    private List<DepUserModel> depUserCol = new List<DepUserModel>();

    private void InitTestData()
    {
    }

    /// <summary>
    /// 完成因撤销部门的操作引起的与人员的交互,需要去除相应的关系
    /// </summary>
    /// <param name="depId"></param>
    /// <returns></returns>
    public bool DeleteDep(string depId)
    {
        //请注意:为了演示简单,部门撤销后,
        //原部门的人员怎么处理等后续业务处理,这里就不管了

        //1:到记录部门和人员关系的集合里面,寻找跟这个部门相关的人员
        //设置一个临时的集合,记录需要清除的关系对象
        var tempCol = new List<DepUserModel>();
        foreach (var du in depUserCol)
        {
            if(du.DepId== depId)
                //需要把这个相关的记录去掉,先记录下来
                tempCol.Add(du);
        }
        //从关系集合里面清除掉这些关系
        foreach (var item in tempCol)
        {
            depUserCol.Remove(item);
        }

        return true;
    }

    /// <summary>
    /// 完成因人员离职引起的与部门的交互
    /// </summary>
    /// <param name="userId">离职人员的编号</param>
    /// <returns></returns>
    public bool DeleteUser(string userId)
    {
        //到记录部门和人员关系的集合里面,寻找跟这个人员相关的部门
        //设置一个临时的集合,记录需要清除的关系对象
        var tempCol = new List<DepUserModel>();
        foreach (var du in depUserCol)
        {
            if (du.UserId == userId)
                //需要去掉这个记录,先记录下来
                tempCol.Add(du);
        }
        //从关系集合清除掉这些关系
        foreach (var item in tempCol)
        {
            depUserCol.Remove(item);
        }
        return true;
    }

    /// <summary>
    /// 测试用,打印部门下所有人员
    /// </summary>
    /// <param name="dep"></param>
    public void ShowDepUsers(Dep dep)
    {
        foreach (var item in depUserCol)
        {
            if(item.DepId== dep.DepId)
                Console.WriteLine(item.UserId);
        }
    }

    /// <summary>
    /// 打印一个人员所属的部门
    /// </summary>
    /// <param name="user"></param>
    public void ShowUserDeps(User user) { }
    /// <summary>
    /// 交换部门
    /// </summary>
    /// <param name="userId"></param>
    /// <param name="oldDepId"></param>
    /// <param name="newDepId"></param>
    /// <returns></returns>
    public void ChangeDep(string userId,string oldDepId,string newDepId){}

    /// <summary>
    /// 合并后新的部门对象
    /// </summary>
    /// <param name="colDepIds"></param>
    /// <param name="newDep"></param>
    public void JoinDep(List<string> colDepIds, Dep newDep)
    {
    }
}

中介者模式的优缺点

优点:

  • 松散耦合
    中介者模式通过把多个同事对象之间的交互封装到中介者对象里面,从而使得同事对象之间松散耦合,基本上可以做到互不依赖。这样一来,同事对象就可以独立地变化和复用,而不再像以前那样“牵一发而动全身”了。
  • 集中控制交互
    多个同事对象的交互,被封装在中介者对象里面集中管理,使得这些交互行为发生变化的时候,只需要修改中介者对象就可以了,当然如果是已经做好的系统,那就扩展中介者对象,而各个同事类不需要做修改。
  • 多对多变成一对多没有使用中介者模式的时候,同事对象之间的关系通常是多对多的,引入中介者对象以后,中介者对象和同事对象的关系通常变成了双向的一对多,这会让对象的关系更容易理解和实现。

缺点:

中介者模式的一个潜在缺点是,过度集中化。如果同事对象的交互非常多,而且比较复杂,当这些复杂性全部集中到中介者的时候,会导致中介者对象变得十分复杂,而且难于管理和维护。

思考中介者模式

中介者模式的本质:封装交互。

中介者模式的目的,就是用来封装多个对象的交互,这些交互的处理多在中介者对象里面实现。因此中介对象的复杂程度,就取决于它封装的交互的复杂程度。

只要是实现封装对象之间的交互功能,就可以应用中介者模式,而不必过于拘泥于中介者模式本身的结构。标准的中介者模式限制很多,导致能完全按照标准使用中介者模式的地方并不是很多,而且多集中在界面实现上。只要本质不变,稍稍变形一下,简化一下,或许能更好地使用中介者模式。

何时选用中介者模式

  • 如果一组对象之间的通信方式比较复杂,导致相互依赖、结构混乱,可以采用中介者模式,把这些对象相互的交互管理起来,各个对象都只需要和中介者交互,从而使得各个对象松散耦合,结构也更清晰易懂。
  • 如果一个对象引用很多的对象,并直接跟这些对象交互,导致难以复用该对象,可以采用中介者模式,把这个对象跟其他对象的交互封装到中介者对象里面,这个对象只需要和中介者对象交瓦就可以了。
posted @ 2023-07-10 16:23  huihui_teresa  阅读(58)  评论(0)    收藏  举报