Unity中运用PureMVC框架


目录


一、理解PureMVC

PureMVC 的框架如图所示:

PureMVC

PureMVC 框架

如果你曾看过 GOF (Group of Four,即四人帮)关于设计模式的书籍,那么你绝对遇到过图中的大多数术语。我虽然没有看过这本书,但是我看过 《设计模式与游戏完美开发》 一书,在这本书中,我就接触到了外观模式(Facade)、命令模式、代理模式(Proxy)、观察者模式的运用,我在此只列举出了一些模式,如果你感兴趣,可以找这本书看看,这本书是从 《P级阵地》 ——一款塔防游戏入手开始讲解设计模式是如何运用到游戏中的,我相信看过这本书的家伙一定受益匪浅。
回到我们的主题PureMVC,图中有 Model、View和Controller三个经典要素,除此之外,我们还能看见 Facade,说明其运用了外观模式的思想。我们再看看 Facade 管理哪些元素——Mediator、Command和Proxy。
我相信注意力惊人的读者应该早就注意到 Mediator 其实是和 View 有关的;Proxy 和 Model 有关;Command 和 Controller 有关。
属于 View Component(视图组件)的UI元素和 Mediator 相联系;属于 Data Objects(数据对象)的元素和 Proxy 相联系;而 Mediator 和 Proxy 通过 Command 相互联系(就像一般的MVC框架中 Model 和 View 通过 Controller 互相联系一样)。
在PureMVC中,我们不直接使用 Model、View和Controller,而是使用 Proxy、Mediator 和 Command互相配合实现UI功能。
你是否注意到了,我其实没有提及到图中的所有元素! 我忽略了将各个元素连接的线,这些线表示 通知(Notification)。
在继续讲解之前,我先把 PureMVC 框架源代码的获取途径先展示出来,大家可以访问The PureMVC framework寻找源代码仓库链接和官方说明文档。这是其API文档,Live Documenter API Documentation

官方说明文档有各种语言的版本,可以按需下载;大家能通过读文档了解到框架各个类的职责和如何使用该框架。


二、如何在Unity使用PureMVC

该项目出自唐老狮的MVC框架的讲解视频。本文示例项目是创建一个主面板,主面板上面会显示玩家姓名、等级、金钱、钻石和体力值等元素。主面板可以通过按键 M 显示,N隐藏。
当点击主面板上的角色按钮时,会打开角色面板,在角色面板中会显示玩家等级、体力值、攻击力、防御值、闪避值、暴击率和幸运值等数据。角色面板可以按右上角的X隐藏。
角色面板上有一个升级按钮,按下后角色的等级、体力值等都会改变,面板上也需要显示。

gif

项目示例

1. 从简单的开始

首先,我们仍然需要创建一个具体的数据类,也就是开篇给出的图中的 Data Objects。如果这个数据是和玩家相关的,那么我们不妨命名为PlayerDataObj,这个脚本中我们一般只放数据相关的成员,例如玩家的生命值、金钱和攻击力等等。由于这是个数据类,所以不必继承Monobehaviour。在本文示例中的代码如下:

/// <summary>
/// 玩家数据类
/// </summary>
public class PlayerDataObj 
{
    public string name;
    public int lev;
    public int money;
    public int gem;
    public int power;

    public int hp;
    public int atk;
    public int def;
    public int miss;
    public int crit;
    public int luck;
}

接着,我们需要创建UI面板类,即和视图相关的类,在这个类中我们存放的是UI控件,以及对UI控件更新的成员方法,我当前展示的是主面板NewMainView的脚本,代码如下:

/// <summary>
/// 主面板视图
/// </summary>
public class NewMainView : MonoBehaviour
{
    // UI 文本控件 和 按钮控件
    public Text txtName;
    public Text txtLev;
    public Text txtMoney;
    public Text txtPower;
    public Text txtGem;

    public Button btnRole;

    /// <summary>
    /// 更新 UI文本控件内容
    /// </summary>
    /// <param name="data">玩家数据</param>
    public void UpdateInfo(PlayerDataObj data)
    {
        txtName.text = data.name;
        txtLev.text = data.lev.ToString();
        txtMoney.text = data.money.ToString();
        txtPower.text = data.power.ToString();
        txtGem.text = data.gem.ToString();
    }
}

同理,创建角色面板,代码如下:

/// <summary>
/// 角色面板视图
/// </summary>
public class NewRoleView : MonoBehaviour
{
    // UI 文本控件以及按钮控件
    public Text txtLev;
    public Text txtHp;
    public Text txtAtk;
    public Text txtDef;
    public Text txtCrit;
    public Text txtMiss;
    public Text txtLuck;

    public Button btnClose;
    public Button btnLevUp;

    /// <summary>
    /// 更新面板数据
    /// </summary>
    /// <param name="data">玩家数据</param>
    public void UpdateInfo(PlayerDataObj data)
    {
        txtLev.text = data.lev.ToString();
        txtHp.text = data.hp.ToString();
        txtAtk.text = data.atk.ToString();
        txtDef.text = data.def.ToString();
        txtCrit.text = data.crit.ToString();
        txtMiss.text = data.miss.ToString();
        txtLuck.text = data.luck.ToString();
    }
}

这样,我们就完成了UI控件和Obj,对应图中的UI和Obj元素。以后如果有敌人数据,也应该创建类似玩家数据的EnemyDataObj类;如果有新面板,也应该创建新的面板类,假如有一个技能面板,就应该创建一个专门管理该面板上的UI元素的SkillView类。其实到这里,虽然没有用上PureMVC框架的内容,但是也感觉到了一点——解耦的开端。

2. 实现玩家数据类的代理类

我创建了一个名为PlayerProxy的脚本,代码如下:

/// <summary>
/// 玩家数据类对于的代理类
/// </summary>
public class PlayerProxy : Proxy
{
    public new const string NAME = "PlayerProxy";

    // 构造函数,Facade会管理这个类
    public PlayerProxy() : base(NAME)
    {
        PlayerDataObj data = new PlayerDataObj();

        // 初始化好一个数据,并交付给 Proxy 的 Data
        data.name = PlayerPrefs.GetString("PlayerName", "Turok");
        data.money = PlayerPrefs.GetInt("PlayerMoney", 100);
        data.gem = PlayerPrefs.GetInt("PlayerGem", 99);
        data.power = PlayerPrefs.GetInt("PlayerPower", 200);

        data.lev = PlayerPrefs.GetInt("PlayerLev", 1);
        data.hp = PlayerPrefs.GetInt("PlayerHp", 10);
        data.atk = PlayerPrefs.GetInt("PlayerAtk", 10);
        data.def = PlayerPrefs.GetInt("PlayerDef", 5);
        data.crit = PlayerPrefs.GetInt("PlayerCrit", 8);
        data.miss = PlayerPrefs.GetInt("PlayerMiss", 2);
        data.luck = PlayerPrefs.GetInt("PlayerLuck", 3);

        Data = data; // 里氏替换原则,用父类容器装子类
    }

    /// <summary>
    /// 升级
    /// </summary>
    public void LevUp()
    {
        PlayerDataObj data = Data as PlayerDataObj;

        data.lev += 1;

        data.hp += data.lev;
        data.atk += data.lev;
        data.def += data.lev;
        data.crit += data.lev;
        data.miss += data.lev;
        data.luck += data.lev;
    }

    public void SaveData()
    {
        PlayerDataObj data = Data as PlayerDataObj;

        PlayerPrefs.SetString("PlayerName", data.name);
        PlayerPrefs.SetInt("PlayerLev", data.lev);
        PlayerPrefs.SetInt("PlayerGem", data.gem);
        PlayerPrefs.SetInt("PlayerMoney", data.money);
        PlayerPrefs.SetInt("PlayerPower", data.power);

        PlayerPrefs.SetInt("PlayerHp", data.hp);
        PlayerPrefs.SetInt("PlayerAtk", data.atk);
        PlayerPrefs.SetInt("PlayerDef", data.def);
        PlayerPrefs.SetInt("PlayerCrit", data.crit);
        PlayerPrefs.SetInt("PlayerMiss", data.miss);
        PlayerPrefs.SetInt("PlayerLuck", data.luck);
    }
}

在此需要说明几点:

  1. PlayerProxy类是负责我们之前创建的PlayerDataObj类的,正如开篇给出的框架总图中,Proxy和Obj有着联系。
  2. PlayerProxy需要继承Proxy类,并实现其中的有参构造函数。
  3. 关于这个有参构造函数,public Proxy(string proxyName, object data = null),参数proxyName表示这个代理的名字,data如果未传入,则默认值为null,我们传入的是数据类,在这我们需要传入的就是PlayerDataObj对象。如果我们没有传入,那我们需要在内部创建数据类对象,并执行一些初始化操作,就像代码中所做的那样。
  4. 在有参构造函数中,有一句Data = data;,其中data是我们实例化的PlayerDataObj对象,但是Data是属性,public object Data { get; set; },表示代理的数据。其实这个有参构造函数做了两件事情,第一件事情:赋予这个代理一个名字(相当于标识符了,有名字才能通过Facade快速找到);第二件事情:存好Data,相当于建立这个 代理 和 一个 数据之间的联系。
  5. LevUp()、SaveData()这两个方法,是对数据的操作,以前我们使用MVC思想,把数据和对数据的操作放在了同一个 Model 模块,现在我们解耦,把数据放一个地方,把对数据的操作放在了 这个代理处,怎么样,是不是有点解耦的感觉了。
  6. 在LevUp()、SaveData()两个方法中,有Data as PlayerDataObj的转换,是因为Data是object类型的,需要转换成对应类型后才能使用。
  7. public new const string NAME = "PlayerProxy";中的new关键字表示重写了父类Proxy的NAME成员。

3. 实现一个通知类

如果你查看过官网上Implementation Idioms and Best Practices的PDF文件,你会看到其中使用ActionScript3实现的程序中,通知常量是直接放在ApplicationFacade类(实现的Facade)中的。
在本示例项目中,将单独创建一个类,存储通知名。代码如下:

/// <summary>
/// PureMVC 中声明各种 “通知名” 的类
/// </summary>
public class PureNotification 
{
    /// <summary>
    /// 开始
    /// </summary>
    public const string START_UP = "Start_Up";
    /// <summary>
    /// 显示面板
    /// </summary>
    public const string SHOW_PANEL = "Show_Panel";
    /// <summary>
    /// 更新主面板信息
    /// </summary>
    public const string UPDATE_MAINVIEW_INFO = "Update_MainView_Info";
}

在此也需要说明:

  1. 该类中的所有成员都是常量,意味着我们无法更改它们;
  2. 在《PureMVC_IIBP_Chinese》中,提及到 “ 当这些Notification 的名称常量需要被其他的程序访问时,我们可以使用单独的“ApplicationConstants”类来存放这些 Notification 名称常量定义。 不管什么时候,都应该把Notification(通知)名称定义为常量,需要引用一个Notification 时就使用它的名称常量,这样做可以避免一些编译时无法 发现的错误。因为编译器可以检查常量;而使用字符串,如果你手误输入错 误的字符串,编译器也不法知道,也无从报错。 ” ,其中就有我们创建这个PureNotification类的原因。
  3. 变量名使用全大写,下划线分隔,是遵循命名规范。

4. 为主面板和角色面板编写Mediator类

在前面,我们为PlayerDataObj类写了对应的PlayerProxy类。现在,我们将为NewMainView和NewRoleView编写对应的NewMainViewMediator和NewRoleViewMediator类。
Mediator 对象的职责是操作对应的面板的组件,例如NewMainViewMediator直接操作NewMainView中的文本控件。
Mediator保存了一个或者多个 View Component的引用,在Unity中就是 UGUI的Text、Button、Image等组件,通过Mediator的属性ViewComponent来管理这些组件。
我先给出NewMainViewMediator和NewRoleViewMediator的相关代码,然后再对具体的代码进行说明:

/// <summary>
/// 主视图的 Mediator
/// </summary>
public class NewMainViewMediator : Mediator
{
    // Mediator 的名字
    public new static string NAME = "NewMainViewMediator";
    
    // 构造函数
    // 父类 Mediator 的构造函数中执行的操作如下:
    // 1. 设置 Mediator 的名字
    // 2. 设置 ViewComponent 的内容,ViewComponent即视图组件,在这里就是UGUI中的Text、Button组件等
    public NewMainViewMediator() : base(NAME)
    {

    }

    // 列出 Mediator 感兴趣的通知名称
    // 返回值是一个 string数组,里面就存放我们需要注意的通知名
    public override string[] ListNotificationInterests()
    {
        // 像事件需要传入方法名一样,这里传入需要监听的通知名
        return new string[]
        {
            PureNotification.UPDATE_MAINVIEW_INFO
        };
    }

    // Mediator 处理通知的逻辑
    // 传入一个 INotification 对象,这个对象中包含了名称(Name)和 相关的内容(也被称作主体,Body)
    // 在Implementation Idioms and Best Practices 中,关于该方法的使用,有提及最好使用switch条件判断语句来处理不同通知的逻辑
    public override void HandleNotification(INotification notification)
    {
        // 在上面的 ListNotificationInterests()方法中监听了通知
        // 在本方法中则处理监听的通知
        switch (notification.Name)
        {
            case "Update_MainView_Info":
                // 当执行这个通知中的逻辑时,将更新主面板的数据
                (ViewComponent as NewMainView).UpdateInfo(notification.Body as PlayerDataObj);
                break;
        }
    }
}
/// <summary>
/// 角色面板的 Mediator
/// </summary>
public class NewRoleViewMediator : Mediator
{
    public new static string NAME = "NewRoleViewMediator";

    public NewRoleViewMediator() : base(NAME)
    {

    }

    public override string[] ListNotificationInterests()
    {
        // 像事件需要传入方法名一样,这里传入需要监听的通知名
        return new string[]
        {
            PureNotification.UPDATE_MAINVIEW_INFO
        };
    }

    public override void HandleNotification(INotification notification)
    {
        // 在上面的 ListNotificationInterests()方法中监听了通知
        // 在本方法中则处理监听的通知
        switch (notification.Name)
        {
            case "Update_MainView_Info":
                // 当执行这个通知中的逻辑时
                (ViewComponent as NewRoleView).UpdateInfo(notification.Body as PlayerDataObj);
                break;
        }
    }
}

在这里说明:

  1. 继承Mediator类以实现一个具体的Mediator。
  2. 该类使用new关键字覆盖了父类的NAME属性。
  3. 父类中的构造函数中做了两件事情,第一件事情:赋予这个具体的Mediator类名字;第二件事情:设置 ViewComponent,视图上的相关组件。
  4. ListNotificationInterests()列出了这个具体的Mediator感兴趣的通知,像 监听事件一样。
  5. HandleNotification()中表示如果收到通知,具体的Mediator应该要根据通知名做出不同的行为。

需要强调的是,如果 ListNotificationInterests() 中没有添加感兴趣的通知,那么就算HandleNotification()中有接收这个通知后执行的逻辑,也不会执行!如果HandleNotification()没有写接收这个通知后执行的逻辑,即使添加了感兴趣的通知,也不会做任何事!

5. 开始写命令 Command

在完成数据层的代码后,我们紧接着写了视图层的代码,也就是说我们已经依次完成了Proxy和Mediator相关的部分。接下来,我们将完成Command的部分。
在给出代码前,我希望读者能理解 Command 的职责:

  • 在背后默默工作的家伙——处理一些视图和数据层交互的逻辑。
  • 专门做某一件事情,因为 Command 处理的逻辑本来就很多,如果所有事情都交给它来做,它会累垮的——一个 Command 负责 一件事。

下面是 StartUpCommand,ShowPanelCommand、LevUpCommand的代码,以及更新后的PureNotification代码:
更新后的PureNotification代码,添加了 LEV_UP和HIDE_PANEL字符串。

/// <summary>
/// PureMVC 中声明各种 “通知名” 的类
/// </summary>
public class PureNotification 
{
    /// <summary>
    /// 开始
    /// </summary>
    public const string START_UP = "Start_Up";
    /// <summary>
    /// 显示面板
    /// </summary>
    public const string SHOW_PANEL = "Show_Panel";
    /// <summary>
    /// 隐藏面板
    /// </summary>
    public const string HIDE_PANEL = "Hide_Panel";
    /// <summary>
    /// 更新主面板信息
    /// </summary>
    public const string UPDATE_PLAYER_INFO = "Update_Player_Info";
    /// <summary>
    /// 升级
    /// </summary>
    public const string LEV_UP = "Lev_Up";
}

下面是StartUpCommand的代码:

public class StartUpCommand : SimpleCommand
{
    // 1. 继承SimpleCommand类
    // 2. 重写Execute()方法
    public override void Execute(INotification notification)
    {
        // 命令模式,外部如果发送了通知,就会执行这里面的逻辑
        // notification 中存储着 Name 和 Body 等重要信息
        base.Execute(notification);

        // 开始时,需要注册 数据的代理 并初始化数据
        if (!Facade.HasProxy(PlayerProxy.NAME))
        {
            Facade.RegisterProxy(new PlayerProxy());
        }
    }
}

在此需要说明:

  1. 我们编写的某个具体的 Command 类,在这里指的就是 StartUpCommand,需要继承 SimpleCommand 类。
  2. 重写 Execute() 方法,这个 Command 会在 全局唯一的 Facade 中 注册,注册时会将一个通知名和一个命令作为参数传入,这样一来,如果某处发送了这个通知名,就会执行对应的命令中的逻辑。

以下是ShowPanelCommand的代码:

// 显示面板的命令
public class ShowPanelCommand : SimpleCommand
{
    public override void Execute(INotification notification)
    {
        base.Execute(notification);

        // notification.Body 中存放的应为我们想创建的面板的名字
        // 使用字符串变量存储下该信息,并根据该信息来决定创建哪个面板
        string panelName = notification.Body.ToString();

        switch (panelName)
        {
            case "MainPanel":
                // 显示主面板的逻辑
                // 如果没有创建并注册 NewMainViewMediator
                if (!Facade.HasMediator(NewMainViewMediator.NAME))
                {
                    // 注册 NewMainViewMediator
                    Facade.RegisterMediator(new NewMainViewMediator());
                }

                NewMainViewMediator mainViewMediator = Facade.RetrieveMediator(NewMainViewMediator.NAME) as NewMainViewMediator;

                // 如果 mainViewMediator 中的 ViewComponent 为空
                // 表明我们没有创建面板,也没有建立 MainView 和 MainViewMediator 间的联系
                if (mainViewMediator.ViewComponent == null)
                {
                    // 1. 同步加载 MainPanel 资源;2. 实例化 MainPanel 到场景中;3. 用 MainViewMediator 的 ViewComponent 保存 MainView
                    // 第3步是为了建立起 UI面板和 Mediator 间的联系
                    GameObject mainPanelAssets = Resources.Load<GameObject>("UI/MainPanel");
                    GameObject mainPanel = GameObject.Instantiate(mainPanelAssets, GameObject.Find("Canvas").transform);
                    // mainViewMediator.ViewComponent = mainPanel.GetComponent<NewMainView>();
                    // NewMainViewMediator 的 SetView()不仅建立起 UI控件和 Mediator 的联系
                    // 还为按钮绑定了事件
                    mainViewMediator.SetView(mainPanel.GetComponent<NewMainView>());
                }

                // 显示面板后,更新数据
                // 发送通知时,通知中的 Body 存放的是数据
                SendNotification(PureNotification.UPDATE_PLAYER_INFO, Facade.RetrieveProxy(PlayerProxy.NAME).Data);

                break;

            case "RolePanel":
                // 显示角色面板的逻辑
                if (!Facade.HasMediator(NewRoleViewMediator.NAME))
                {
                    Facade.RegisterMediator(new NewRoleViewMediator());
                }

                NewRoleViewMediator roleViewMediator = Facade.RetrieveMediator(NewRoleViewMediator.NAME) as NewRoleViewMediator;

                if (roleViewMediator.ViewComponent == null)
                {
                    GameObject rolePanelAssets = Resources.Load<GameObject>("UI/RolePanel");
                    GameObject rolePanel = GameObject.Instantiate(rolePanelAssets, GameObject.Find("Canvas").transform);
                    //roleViewMediator.ViewComponent = rolePanel.GetComponent<NewRoleView>();
                    roleViewMediator.SetView(rolePanel.GetComponent<NewRoleView>());
                }

                SendNotification(PureNotification.UPDATE_PLAYER_INFO, Facade.RetrieveProxy(PlayerProxy.NAME).Data);
                break;
        }
    }

    
}

在此说明:

  1. 代码中Facade.RegisterMediator(new NewMainViewMediator());是注册好 Mediator,这样才能使用,后续不管是Mediator、Command,还是Proxy,都能够通过全局唯一的Facade访问,Facade能够通过名字查找对应的东西。
  2. SendNotification(),第一个参数是通知名,第二个参数是可选的,是我们想传递的数据。到时候我们会在 notification.Body 中获取这个数据。
  3. RetrieveProxy() 能通过名称获取指定的 Proxy,同理,还可以获得 Mediator和 Command。

LevUpCommand的代码:

public class LevUpCommand : SimpleCommand
{
    public override void Execute(INotification notification)
    {
        base.Execute(notification);

        // 得到 PlayerProxy, 使用其中的 LevUp() 方法
        PlayerProxy tempPlayerProxy = Facade.RetrieveProxy(PlayerProxy.NAME) as PlayerProxy;
        if (tempPlayerProxy != null)
        {
            tempPlayerProxy.LevUp();
            SendNotification(PureNotification.UPDATE_PLAYER_INFO, tempPlayerProxy.Data);
        }
    }
}

6. 完成 Facade 类

Facade 即外观模式。
我们需要创建一个 GameFacade 类,来管理我们创建的 Proxy、Mediator和Command。
代码如下:

/// <summary>
/// 全局唯一的 Facade,它管理着 Proxy、Mediator 和 Command
/// </summary>
public class GameFacade : Facade
{
    // 1. 继承Facade类
    // 2. 手动实现一个单例
    public static GameFacade Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new GameFacade();
            }
            // instance 是 IFacade 类型的,因此为了使用 我们这个GameFacade,需要转换
            return instance as GameFacade;
        }
    }

    // 3. InitializeController() 初始化Controller
    protected override void InitializeController()
    {
        base.InitializeController();

        // 注册命令 Command
        // 以后发送 START_UP 通知,就会执行 这个 Lambda表达式的逻辑
        RegisterCommand(PureNotification.START_UP, () =>
        {
            return new StartUpCommand();
        });

        // 注册 SHOW_PANEL 通知
        RegisterCommand(PureNotification.SHOW_PANEL, () =>
        {
            return new ShowPanelCommand();
        });

        RegisterCommand(PureNotification.HIDE_PANEL, () =>
        {
            return new HidePanelCommand();
        });

        RegisterCommand(PureNotification.LEV_UP, () =>
        {
            return new LevUpCommand();
        });
    }

    // 4. 需要有一个启动函数供外部使用,这个函数内部就执行发送通知的逻辑
    public void StartUp()
    {
        // 执行了 StartUp()方法后,就会发送 START_UP 通知,就会执行 StartUpCommand 的逻辑
        SendNotification(PureNotification.START_UP);
    }
}

在此说明:

  1. GameFacade 需要继承 Facade 类。
  2. 手动设置单例。
  3. InitializeController() 能初始化 Controller,在这个方法内部,我们通常会为注册命令。
  4. 一个启动函数,里面执行的逻辑根据需要决定。
  5. 如果有需求,可以实现 OnRegister() 方法。

总结

由于我的水平有限,以文章的方式讲解并不能很好地解释清楚 PureMVC 的思想,如有更好的见解,可以联系我的邮箱 turokdrake@outlook.com。

Facade:

  • 全局唯一的对象,实现的是外观模式,隐藏了背后复杂的逻辑,直接管理着 Proxy、Command和Mediator。
  • 它不仅可以初始化 Controller,还可以初始化 Command和Mediator。
  • 初始化Controller的逻辑中,也就是InitializeController()方法中,会提前注册一些通知以及对应的命令。
  • 它需要提供一个启动方法,供外部调用。

Proxy:

  • 代理(Proxy)对应着一个数据对象(Data Obj)。
  • 代理中会有一些修改数据的方法。
  • 代理需要注册后(RegisterProxy())才能通过Facade获取。
  • 代理需要有一个名称,这个名称需要在构造函数中初始化。由于需要确保该代理的唯一性,这个名称不得更改。Facade找代理就是根据名字寻找的。
  • 代理的构造函数中有时候还会执行一些数据对象的初始化工作。(当然也可以在实例化这个代理时,传入数据对象。
  • Proxy类中有一个属性 object Data,这个属性可以存放数据对象。

Mediator:

  • 媒介(Mediator)对应着一个视图/面板(View)。
  • 媒介中需要有一个名称,这个名称也在构造函数中初始化。和Proxy一样。
  • Mediator如果需要使用,也需要注册(RegisterMediator()),注册后才能通过Facade管理。
  • 媒介中需要使用ListNotificationInterests()方法列出该媒介感兴趣的通知,只有这样,在某处发出通知时,媒介才会执行HandleNotification()的逻辑。
  • HandleNotification()中是根据通知名来判断要执行哪块逻辑的。
  • Mediator类中有一个属性 object ViewComponent,这个属性存放的是视图类,这意味着它可以随时在 ViewComponent 中修改视图类中的控件。

Command:

  • 命令(Command)是一个逻辑,一个命令干一件事(当然,如果几件事情放在一起做更好的话,也可以一起做)。
  • 命令中需要重写 Execute(INotification notification)方法,这是命令模式。notification就是封装的请求。
  • notification中有 Name属性,这是通知的名字;Body属性,是发送通知时,顺带传入的内容。Body的内容是我们根据需要决定的,比如我发送通知时,需要修改 数据层,那么我就传输数据;如果我不需要修改任何东西,那么不传也行。
  • Command需要注册(RegisterCommand()),这样就能被Facade管理。

Notification:

  • 通知,通知一定是个常量,而且全局唯一。
  • 有时候会将通知放在 Facade中一起管理,也可以把通知放在一个类中管理。