无废话C#设计模式之二十:Mediator

意图

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

场景

我们知道,一个网络游戏往往有很多大区。每一个大区可以是一组服务器,也可以是多组服务器,在这里假设一个大区是一组服务器。为了效率,一般每个大区都会有一个数据库,玩家的创建角色、充值、消费行为只是在这一个大区中有效。现在公司有了新的需求,那就是玩家的一些信息能在多个大区中共享。比如,在注册的时候就把玩家的账户信息写入多个信息共享的大区,玩家在某个大区中充值需要“通知”其它大区修改账户余额,玩家在某个大区中消费也需要“通知”其它大区修改账户余额。

如果我们现在有ABC三个大区,下面的方法可以实现需求:

l         网站的注册方法调用A、B和C大区的注册方法

l         A大区的充值方法调用B和C的充值方法

l         B大区的充值方法调用A和C的充值方法

l         C大区的充值方法调用A和B的充值方法

l         A大区的消费方法调用B和C的充值方法

l         B大区的消费方法调用A和C的充值方法

l         C大区的消费方法调用A和B的充值方法

我想,没有人会这么做吧。你肯定会想到在一个统一的地方去维护所有大区的信息,任何一个大区的行为不直接和其它大区的行为关联,它们所有的行为都提交到一个统一的地方(假设它是AccountSystem)去处理。这么做有几个好处:

l         各大区不需要关心还有哪些其它的大区,它只直接和AccountSystem对话。

l         只需要调整AccountSystem就能调整各大区之间的交互行为,比如我们仅仅希望A和B大区共享信息、C和D大区共享信息,那么对于这种交互策略的改变也需要修改AccountSystem。

l         有利于大区的扩充,有了新的大区后,我们不用在大区中考虑它的交互行为,统一交给AccountSystem去安排。

现在,再来看看引入AccountSystem后的通讯:

l         网站调用AccountSystem的注册方法(1)

l         AccountSystem调用A、B和C大区的注册方法(2)

l         A、B和C大区的充值方法调用AccountSystem的充值方法(3)

l         A、B和C大区的消费方法调用AccountSystem的充值方法(4)

l         AccountSystem的充值方法调用A、B和C大区的专有充值方法(只针对本大区的充值)(5)

l         AccountSystem的充值方法调用A、B和C大区的专有消费方法(只针对本大区的消费)(6)

至此,你已经实现了中介者模式。你可能会觉得,(1)和(2)非常类似门面模式,没错,它确实就是门面模式,而有了(3)~(6)的行为,AccountSystem也就是一个中介者的角色了。

示例代码

using System;

using System.Collections.Generic;

using System.Text;

namespace MediatorExample

{

    class Program

    {

        static void Main(string[] args)

        {

            AccountSystem accountSystem = new AccountSystem();

            GameSystem gameArea1 = new GameArea1(accountSystem);

            GameSystem gameArea2 = new GameArea2(accountSystem);

            accountSystem.RegisterGameArea(gameArea1);

            accountSystem.RegisterGameArea(gameArea2);

            string userName = "aaa";

            accountSystem.CreateAccount(userName);

            gameArea1.Recharge(userName, 200);

            gameArea2.Consume(userName, 50);

            accountSystem.QueryBalance(userName);

        }

}

    class AccountSystem

    {

        private Dictionary<string, int> userBalance = new Dictionary<string, int>();

        private List<GameSystem> gameAreaList = new List<GameSystem>();

        public void RegisterGameArea(GameSystem gs)

        {

            gameAreaList.Add(gs);

        }

        public void CreateAccount(string userName)

        {

            userBalance.Add(userName, 0);

            foreach (GameSystem gs in gameAreaList)

                gs.CreateAccountSelf(userName);

        }

        public void Recharge(string userName, int amount)

        {

            if (userBalance.ContainsKey(userName))

            {

                bool ok = true;

                foreach (GameSystem gs in gameAreaList)

                    ok = gs.RechargeSelf(userName, amount);

                if (ok)

                    userBalance[userName] += amount;

            }

        }

        public void Consume(string userName, int amount)

        {

            if (userBalance.ContainsKey(userName))

            {

                bool ok = true;

                foreach (GameSystem gs in gameAreaList)

                    ok = gs.ConsumeSelf(userName, amount);

                if (ok)

                    userBalance[userName] -= amount;

            }

        }

        public void QueryBalance(string userName)

        {

            Console.WriteLine("Your balance is " + userBalance[userName]);

        }

    }

    abstract class GameSystem

    {

        private AccountSystem accountSystem;

        protected Dictionary<string, int> userBalance = new Dictionary<string, int>();

        public GameSystem(AccountSystem accountSystem)

        {

            this.accountSystem = accountSystem;

        }

        internal virtual bool CreateAccountSelf(string userName)

        {

            userBalance.Add(userName, 0);

            return true;

        }

        internal virtual bool RechargeSelf(string userName, int amount)

        {

            if (userBalance.ContainsKey(userName))

                userBalance[userName] += amount;

            return true;

        }

        internal virtual bool ConsumeSelf(string userName, int amount)

        {

            if (userBalance.ContainsKey(userName))

                userBalance[userName] -= amount;

            return true;

        }

        public void Recharge(string userName, int amount)

        {

            accountSystem.Recharge(userName, amount);

        }

        public void Consume(string userName, int amount)

        {

            accountSystem.Consume(userName, amount);

        }

    }

    class GameArea1 : GameSystem

    {

        public GameArea1(AccountSystem accountSystem) : base(accountSystem) { }

        internal override bool CreateAccountSelf(string userName)

        {

            Console.WriteLine(userName + " Registered in GameAre1");

            return base.CreateAccountSelf(userName);

        }

        internal override bool RechargeSelf(string userName, int amount)

        {

            base.RechargeSelf(userName, amount);

            Console.WriteLine(userName + "'s amount in GameArea1 is " + userBalance[userName]);

            return true;

        }

        internal override bool ConsumeSelf(string userName, int amount)

        {

            base.ConsumeSelf(userName, amount);

            Console.WriteLine(userName + "'s amount in GameArea1 is " + userBalance[userName]);

            return true;

        }

}

    class GameArea2 : GameSystem

    {

        public GameArea2(AccountSystem accountSystem) : base(accountSystem) { }

        internal override bool CreateAccountSelf(string userName)

        {

            Console.WriteLine(userName + " Registered in GameAre2");

            return base.CreateAccountSelf(userName);

        }

        internal override bool RechargeSelf(string userName, int amount)

        {

            base.RechargeSelf(userName, amount);

            Console.WriteLine(userName + "'s amount in GameArea2 is " + userBalance[userName]);

            return true;

        }

        internal override bool ConsumeSelf(string userName, int amount)

        {

            base.ConsumeSelf(userName, amount);

            Console.WriteLine(userName + "'s amount in GameArea2 is " + userBalance[userName]);

            return true;

        }

    }

}

代码执行结果如下图:

 

代码说明

l         AccountSystem是一个中介者角色,它负责各个同事类之间的交互。要使同事对象参与它的管理,就需要在内部维护一个同事对象的列表。

l         我们看到,AccountSystem的注册、充值和消费方法会遍历相关的同事对象并且调用它们的专有方法进行操作。在全部操作完成之后,它才会更新自己的账户。

l         GameSystem是一个抽象同事。充值和消费方法都有两种。一种是给外部调用的充值和消费方法,一种是给外部调用的,另外一种是给AccountSystem调用的。在对外的方法中,GameSystem仅仅是把这个请求转发给中介者,它自己不做实质性的操作,而在xxxSelf()方法中才做真正的充值、消费操作。

l         GameArea1和GameArea2是具体同事,调用父类构造方法来和中介者关联。

l         中介者模式的特点就是同事自己意识到它需要和一个中介者关联,而在实际的操作过程中,它们只是负责和中介者通讯并且接受中介者的请求,而不再和其它同事发生直接的关联。

何时采用

如果一组接口相对稳定(如果GameArea1和GameArea2的充值方法定义不一样,那么AccountSystem就有点晕眩了)的对象之间的依赖关系错综复杂,依赖关系难以维护,或者会发生变动可以考虑引入中介者模式。

实现要点

l         在C#中可以适用delegate关联中介者和各同事之间的交互行为,这样各同事就不需要直接和中介者进行耦合。

l         中介者模式和观察者模式的区别是,前者应用于多对多杂乱交互行为的统筹处理,后者应用于一(多)对多关系的灵活定制。对于本例来说,集中处理后还需要分散处理,那么后半阶段的处理过程可以应用观察者模式。对于前一节的例子来说,如果有多个主体角色和多个观察者进行多对多通讯的话,也可以应用中介者模式来统筹这个多对多的过程(大家可以自己尝试修改前一节的实例来应用中介者模式)。

l         中介者模式和门面模式的区别是,前者的各同事类需要依靠中介者进行双向通讯,应用于子系统之间,而后者的子系统往往不会通过门面去和调用方进行通讯,趋向于单向通讯,应用于子系列和更高层次的系统。本例中就有门面模式和中介者模式的影子。

l         中介者模式往往可以在构架的层次进行应用,有的时候和观察者模式以及门面模式一起使用,有的时候又会向观察者模式和门面模式退化。其实在应用模式的过程中不必过多考虑模式的准确定位,如果我们确实从中得以,那么这个名字就不重要了。

注意事项

 

l         不是所有的系统都需要应用中介者模式把多对多的关系转化为多对一对多的。如果各个同事之间本来的关联就很清晰(没有交错关联),或这种关联并不复杂,没有必要应用中介者。

l         在实际的应用过程中,中介者做的控制并不会向本例那样简单,它可能包含很多的处理逻辑。如果还伴随着需求的改变,中介者角色可能会越来越难维护,此时可以考虑对中介者角色或处理行为应用其它的一些设计模式。

(原创)无废话C#设计模式之二十:Mediator相关推荐

  1. (原创)无废话C#设计模式之二十二:总结(针对GOF23)

    无废话C#设计模式之二十二:总结(针对GOF23) 比较 设计模式 常用程度 适用层次 引入时机 结构复杂度 Abstract Factory 比较常用 应用级 设计时 比较复杂 Builder 一般 ...

  2. (原创)无废话C#设计模式之十二:Bridge

    无废话C#设计模式之十二:Bridge 意图 将抽象部分与实现部分分离,使它们都可以独立的变化. 场景 还是说我们要做的网络游戏,多个场景需要扩充的问题我们已经采用了创建型模式来解决.现在的问题就是, ...

  3. (原创)无废话C#设计模式之十九:Observer

    无废话C#设计模式之十九:Observer 意图 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新. 场景 这次不说游戏了,假设我们需要在一个W ...

  4. (原创)无废话C#设计模式之十一:Composite

    无废话C#设计模式之十一:Composite 意图 将对象组合成树形结构以表示"部分-整体"的层次结构.Composite模式使得用户对单个对象和组合对象的使用具有一致性. 场景 ...

  5. (原创)无废话C#设计模式之十七:Chain Of Resp.

    无废话C#设计模式之十七:Chain Of Resp. 意图 使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系.将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象能处理 ...

  6. (原创)无废话C#设计模式之四:Factory Method

    无废话C#设计模式之四:Factory Method       <a href='http://xgb.xgb.cc'>a</a>   <a href='http:// ...

  7. 备忘录模式 Memento 快照模式 标记Token模式 行为型 设计模式(二十二)

    备忘录模式 Memento 沿着脚印,走过你来时的路,回到原点. 苦海翻起爱恨 在世间难逃避命运 相亲竟不可接近 或我应该相信是缘份 一首<一生所爱>触动了多少人的心弦,一段五百年都没有结 ...

  8. 模板方法模式 Template method 行为型 设计模式(二十六)

    模板方法模式 Template method 上图为网上百度的一份简历模板截图 相信大家都有求职的经历,那么必然需要简历,写简历的时候,很可能你会网上检索一份简历模板,使用此模板的格式,然后替换为你的 ...

  9. 云计算设计模式(二十四)——仆人键模式

    云计算设计模式(二十四)--仆人键模式 使用一个令牌或密钥,向客户提供受限制的直接訪问特定的资源或服务,以便由应用程序代码卸载数据传输操作. 这个模式是在使用云托管的存储系统或队列的应用中特别实用,而 ...

  10. 云计算设计模式(二十)——调度程序代理管理者模式

    云计算设计模式(二十)--调度程序代理管理者模式 协调一系列在分布式服务集和其它远程资源的的行为,试图透明地处理故障,假设这些操作失败,或撤销,假设系统不能从故障中恢复执行工作的影响.这样的模式能够分 ...

最新文章

  1. MyEclipse教程:Web开发——创建Web片段项目
  2. 诚意租房网blog2
  3. (0010) iOS 开发之UI布局兼容 4s/5/6/7 屏幕解决方案
  4. IP地址莫名其妙变为0.0.0.0
  5. 开发中我们谈的产品化是什么?阿里是怎么看待产品化?
  6. 1443B. Saving the City
  7. 前辈学习C语言的四种方法,实际上不管学什么语言,都行之有效!
  8. LAMP架构调优(四)——资源压缩传输
  9. 如何在 Mac 上的程序坞中使用文件夹?
  10. python使用正则验证电子邮件_在Python中使用正则表达式提取电子邮件地址
  11. RC有源滤波器之低通滤波器(一)
  12. diskgenius创建efi分区_找不到引导分区 启动分区不存在 怎么创建efi系统分区
  13. 攻略 | 教你拿下梦寐以求的Offer(多资源)
  14. ST-LINK Utility + ST LINK+ STM32G474 erase chip fails
  15. Windows凭据管理器
  16. myql 查询树形表结果:说说、说说的评论、评论的回复
  17. 解决AttributeError: module ‘win32com.gen_py.00020813-0000-0000-C000-000000000046x0x1x9‘ has no attribu
  18. Movavi PDF Editor 适用于Mac的多功能PDF编辑器
  19. 【C语言】如何不用中间变量交换变量的值
  20. 《逻辑学》の思维导图(小区域总结,望高人能补充)

热门文章

  1. C#工控上位机实例_HINET智能网关用于三菱FX1S/1N/2N/3S/3G/3UPLC远程编程和上位机监控...
  2. python项目代做_ECS 170代做、代写Python、data代做、代做Python程序代写Web开发|代写Database...
  3. c语言和python的堆栈,python - 在C ++中更快地执行两个程序的可能解释(与Python比较)? - 堆栈内存溢出...
  4. bldc 原理 方波控制_正弦波驱动BLDC原理
  5. 表单元素设置disabled后 支持点击事件
  6. 蓝桥杯2017年第八届C/C++省赛B组第三题-承压计算
  7. JDK指定D盘安装及环境变量配置
  8. Futter基础第5篇: 实现列表、动态列表【ListView、ListView.builder】
  9. 7-4 超速判断 (10 分)
  10. 代码源文件中去掉断元字符^M的方法介绍