对Unity中的PureMVC的理解

在Unity中,PureMVC适合用来做UI上的交互框架。市面上有相当一部分的游戏公司也是采用pureMVC来搭建UI框架。
根据自己初学这个框架的体会,新人学这个框架一定要先有个好的实例看看怎么运行怎么交互的,对这个框架理解才快,单纯看文档也是云里雾里的。 PureMVC在Unity的好的实例不是很容易找,所以这里通过一个简单实例以及官方文档PDF来阐明pureMVC的运行逻辑流程框架的优点以及正确使用框架需要注意的事项,自己以后再回想也不需要重新查找和翻看资料,新学的同学可以打开项目然后边看博客边运行实例对照看看。
首先简单介绍一下PureMVC的概念,PureMVC里面有Mediator,Proxy,Command类,分别对应但是拓展了传统MVC模式的View,Model,Control。 Mediator,Proxy,的实例都是单例模式,在程序中只有一个,关于PureMVC的特点还有:

  • 在PureMVC开发中,一般一定职责范围内的数据需要抽取成一个单纯的数据类,对这个数据类进行相关操作则专门由一个Proxy类的对象负责。
  • PureMVC将视图UI以及对视图UI的操作分离开来了,在PureMVC开发中,单纯的视图类持有各种组件引用以及组件引用的状态,Mediator持有这个视图类,并且处理视图类的组件状态变化引起的功能逻辑。Mediator具体处理内容包括通知proxy更改数据,通过Command来进行与其他Mediator的交互,对数据变化进行监听来更改视图UI的显示等。
  • Command用于执行某个临时的系统级别的交互逻辑,例如启动应用程序或者执行一些涉及到比较多Mediator或者Proxy的逻辑,Command是没有状态的,它只在被需要的时候创建,执行完后(可以简单理解为其Execute()方法执行完后)即被销毁,某个常驻实例长期持有某个command是不正确的做法。Command分为SimpleCommand以及MacroCommand,SimpleCommand用于执行某个任务,按照框架的建议,一般直到SimpleCommand执行结束也不能唤起其他Command的执行,如果需要连续执行多个Command则可以使用MacroCommand,其一般只负责组合并顺序执行一系列的SimpleCommand以及MacroCommand来达到比较复杂的交互逻辑。

运行逻辑流程

简单介绍完pureMVC的特点后,说下实例,实例的功能是显示一个菜单列表,在选中菜单列表某项之后,下面的UI会显示出详情,可以对选中的某项进行修改,也可以新加某一行,也可以删除某一行,也可以取消当前选中项的查看详情。
在实例中,用到的Command分别叫做StartUpCommand和DeleteCommand。StartUpCommand负责Mediator的初始化,DeleteCommand负责删除列表数据的逻辑操作;
用到的Proxy叫做UserProxy,它继承自Proxy类;
用到的Mediator分别是UserListMediator和UserFormMediator,持有的view分别叫做UserList和UserForm,下图中,顶部的UI叫做userList,底部的UI叫做UserForm;
对Unity中的PureMVC的理解_第1张图片
UI结构如下
对Unity中的PureMVC的理解_第2张图片
每一项的变绿是toggle选项实现的,toggle指定了toggleGroup,因为toggleGroup是没有勾选allowSwitchOff,所以数据加载完之后,一定会有一项变绿,即进入选中状态,从而触发pureMVC实现下部UI的详情显示。如果对toggleGroup不了解的人,第一次看代码对怎么选中可能有疑问,因为代码里面找不到最开始是哪里进行选择的,所以这里解释一下。
然后 相对于pureMVC的初始化事件注册来说,unity的列表加载完成要延迟那么几帧,所以即使一开始代码看起来好像明明先完成UserList列表加载的后面才进行UserFrom对选中某项的事件的初始化注册,UserForm还是能对选中的项进行详情显示

程序的开始点是在这里,实例化一个ApplicationFacade,调用其启动函数,将自己MainUI传进去

public class MainUI : MonoBehaviour
{
     
    public UserList userList;
    public UserForm userForm;
    //Lucie Wilde
    void Awake()
    {
     
        //Lucie Wilde
        ApplicationFacade facade = ApplicationFacade.Instance as ApplicationFacade;
        facade.Startup(this);
    }
}

看看ApplicationFacade类

//[lzh]
using UnityEngine;
using System.Collections;
using PureMVC.Patterns;
using PureMVC.Interfaces;

public class ApplicationFacade : Facade
{
     
    /// 
    /// Facade Singleton Factory method.  This method is thread safe.
    /// 
    public new static IFacade Instance
    {
     
        get
        {
     
            if(m_instance == null)
            {
     
                lock(m_staticSyncRoot)
                {
     
                    if (m_instance == null)
                    {
     
                        Debug.Log("ApplicationFacade");
                        m_instance = new ApplicationFacade();
                    }
                }
            }
            return m_instance;
        }
    }

    /// 
    /// Start the application
    /// 
    /// 
    public void Startup(MainUI mainUI)
    {
     
        Debug.Log("Startup() to SendNotification.");
        SendNotification(EventsEnum.STARTUP, mainUI);
    }

    protected ApplicationFacade()
    {
     
        // Protected constructor.
    }

    /// 
    /// Explicit static constructor to tell C# compiler 
    /// not to mark type as beforefieldinit
    ///
    static ApplicationFacade()
    {
     

    }

    protected override void InitializeController()
    {
     
        Debug.Log("InitializeController()");
        base.InitializeController();
        RegisterProxy(new UserProxy());
        RegisterCommand(EventsEnum.STARTUP, typeof(StartupCommand));
        RegisterCommand(EventsEnum.DELETE_USER, typeof(DeleteUserCommand));
    }
}

这个类用一个单例模式实现,它的写法可以参考
关于C#的Lock关键字实现线程安全的单例模式

调用单例模式会实例化一个ApplicationFacader对象,在pureMVC使用惯例中,程序都会有个叫做ApplicationFacader的对象,这个对象只有一个,所以用单例模式,这个对象继承自Facade类,继承自这个类的实例会持有程序中所有继承自Mediator的实例,继承自Proxy类的实例。要直接获取这些实例要通过ApplicationFacade,在创建ApplacationFacade的时候首先会执行InitializeController,按照PureMVC的惯例写法,这里一般先执行所有数据的注册和初始化,然后执行所有Command的注册和初始化,ApplacationFacade的InitializeController重写自Facade,Façade是与Mediator和Proxy这两个核心层通信的唯一接口,以简化开发复杂度。
继承自Facade的类要重写InitializeController这个方法, 案例在这个方法里面做了两件事,

RegisterProxy(new UserProxy());
RegisterCommand(EventsEnum.STARTUP, typeof(StartupCommand));
RegisterCommand(EventsEnum.DELETE_USER, typeof(DeleteUserCommand));

第一句是UserProxy类的实例化及注册
下面两句话对两个继承了Cammand类的类进行注册。
下面是StartUpCommand的内容

//[lzh]
using UnityEngine;
using System.Collections;
using PureMVC.Patterns;
using PureMVC.Interfaces;

public class StartupCommand : SimpleCommand, ICommand
{
     
    public override void Execute(INotification notification)
    {
     
        Debug.Log("StartupCommand.Execute()");
        MainUI mainUI = notification.Body as MainUI;
        Facade.RegisterMediator(new UserListMediator(mainUI.userList));
        Facade.RegisterMediator(new UserFormMediator(mainUI.userForm));
    }
}

继承自Command的类只重写了Execute方法,继承自Command的类也只需重写Execute方法。
上面代码中的Execute方法并不是在下面这句话执行的时候执行的。

RegisterCommand(EventsEnum.STARTUP, typeof(StartupCommand));

注意Application类里面还有一句话

SendNotification(EventsEnum.STARTUP, mainUI);

上面两句话一个是注册StartupCommand,第二个就是对注册的StartupCommand进行唤起,并且唤起时传值了一个mainUI,即文章最开始的代码的MainUI类的实例,在调用SendNotification的时候传什么其实都可以,PureMVC的这个方法,后面可以传1个任意类型的参数,实际运用中传什么参数就是由开发者根据需要进行设计了,如果需要传多个参数的时候,可以将多个参数封装到一个对象里面传进来。
唤起的字符串标记常量,习惯全部写到某个类里面,如下,常量的名字已经代表了它唤起的时机和原因:

public class EventsEnum
{
     
    public const string STARTUP = "startup";

    public const string NEW_USER = "newUser";
    public const string DELETE_USER = "deleteUser";
    public const string CANCEL_SELECTED = "cancelSelected";

    public const string USER_SELECTED = "userSelected";
    public const string USER_ADDED = "userAdded";
    public const string USER_UPDATED = "userUpdated";
    public const string USER_DELETED = "userDeleted";

    public const string ADD_ROLE = "addRole";
    public const string ADD_ROLE_RESULT = "addRoleResult";
}

在例子中,用EventsEnum.STARTUP唤起的时候,PureMVC会实例化一个StartupCommand类的实例,并且执行其Execute方法,在执行完之后就会销毁这个StartupCommand类的实例,PureMVC框架中的任意一个继承自Command的类也是如此

StartupCommand中,Execute方法里面进行了UserListMediator和UserFormMediator的初始化及注册,注意在UserListMediator和UserFormMediator这两个继承自Mediator的类初始化的时候传进了实例,实例是Mediator需要持有的视图类。

简单看了StartupCommand后再回头来看看UserProxy

//[lzh]
using UnityEngine;
using System.Collections.Generic;
using PureMVC.Patterns;
using PureMVC.Interfaces;

public class UserProxy : Proxy, IProxy
{
     
    public new const string NAME = "UserProxy";

    /// 
    /// Return data property cast to proper type
    /// 
    public IList<UserVO> Users
    {
     
        get {
      return (IList<UserVO>) base.Data; }
    }

    public UserProxy()
            : base(NAME, new List<UserVO>())
    {
     
        Debug.Log("UserProxy()");
        // generate some test data			
        AddItem(new UserVO("lstooge", "Larry", "Stooge", "[email protected]", "ijk456", "ACCT"));
        AddItem(new UserVO("cstooge", "Curly", "Stooge", "[email protected]", "xyz987", "SALES"));
        AddItem(new UserVO("mstooge", "Moe", "Stooge", "[email protected]", "abc123", "PLANT"));
        AddItem(new UserVO("lzh", "abc", "def", "[email protected]", "abc123", "IT"));
    }

    /// 
    /// add an item to the data
    /// 
    /// 
    public void AddItem(UserVO user)
    {
     
        Users.Add(user);
        SendNotification(EventsEnum.USER_ADDED, user);

    }

    /// 
    /// update an item in the data
    /// 
    /// 
    public void UpdateItem(UserVO user)
    {
     
        for (int i = 0; i < Users.Count; i++)
        {
     
            if (Users[i].UserName.Equals(user.UserName))
            {
     
                Users[i] = user;
                break;
            }
        }
        SendNotification(EventsEnum.USER_UPDATED, user);
    }

    /// 
    /// delete an item in the data
    /// 
    /// 
    public void DeleteItem(UserVO user)
    {
     
        for (int i = 0; i < Users.Count; i++)
        {
     
            if (Users[i].UserName.Equals(user.UserName))
            {
     
                Users.RemoveAt(i);
                break;
            }
        }
        SendNotification(EventsEnum.USER_DELETED);
    }
}

在UserProxy的构造方法中,调用了基类的构造函数并且传了两个参数

 public new const string NAME = "UserProxy";
 base(NAME, new List<UserVO>())

这是pureMVC的惯例写法,第二个参数是这个userProxy的数据类,会在父类Proxy里面用object类型的变量持有,所有继承自Proxy的类都应该持有属于其的数据类的实例,数据类实例只存储数据,没有任何关于数据的操作 。数据类可以是任何类型的变量,这里是List类型的。也因为这样,要获取Proxy的数据类要自己像下面代码一样写个get的方法,框架是不推荐外部直接获取Proxy所持有的数据类实例的 UserVO就是个简单的用户数据封装,这里就不介绍了。

   public IList<UserVO> Users
    {
     
        get {
      return (IList<UserVO>) base.Data; }
    }

现在来看第一个参数,因为每个proxy和mediator类的实例都是只有一个的。第一个NAME参数是用于实现通过ApplicationFacade得到这个UserProxy的实例的标记,NAME 是个常量,可以通过类名而不是实例名找到它。
下面的代码可以得到UserProxy的实例

userProxy = Facade.RetrieveProxy(UserProxy.NAME) as UserProxy;

这段代码经常出现在继承了Mediator以及Command类的类中,开发者用这种写法来直接获得Proxy;
Mediator以及Command都可以获取需要的Proxy对象直接进行数据操作
Mediator以及Command类都继承自INotifier类,这个类持有Facade变量,指向的是全局只有一个的ApplicationFacade的实例。
在UserProxy中,其他的都是开发者自己写的类似增删改查的方法,
上文中的增删改方法最后都有SendNotification,这些增删改的方法对数据造成改变之后,需要调用SendNotification去通知那些对这些增删改数据感兴趣并注册了的Mediator实例,感兴趣的Mediator实例被通知之后会有所行动,如更新数据在视图中的显示等。

再来看看UserListMediator

using UnityEngine;
using System.Collections;
using PureMVC.Patterns;
using PureMVC.Interfaces;
using System.Collections.Generic;

public class UserListMediator : Mediator, IMediator
{
     
    private UserProxy userProxy;

    public new const string NAME = "UserListMediator";

    private UserList View
    {
     
        get {
      return (UserList)ViewComponent; }
    }

    public UserListMediator(UserList userList)
            : base(NAME, userList)
    {
     
        Debug.Log("UserListMediator()");
        userList.NewUser += userList_NewUser;
        userList.DeleteUser += userList_DeleteUser;
        userList.SelectUser += userList_SelectUser;
    }

    public override void OnRegister()
    {
     
        Debug.Log("UserListMediator.OnRegister()");
        base.OnRegister();
        userProxy = Facade.RetrieveProxy(UserProxy.NAME) as UserProxy;
        View.LoadUsers(userProxy.Users);
    }

    void userList_NewUser()
    {
     
        UserVO user = new UserVO();
        SendNotification(EventsEnum.NEW_USER, user);
    }

    void userList_DeleteUser()
    {
     
        SendNotification(EventsEnum.DELETE_USER, View.SelectedUserData);
    }

    void userList_SelectUser()
    {
     
        Debug.Log(" UserListMediator userList_SelectUser ");
        SendNotification(EventsEnum.USER_SELECTED, View.SelectedUserData);
    }

    public override IList<string> ListNotificationInterests()
    {
     
        IList<string> list = new List<string>();
        list.Add(EventsEnum.USER_DELETED);
        list.Add(EventsEnum.CANCEL_SELECTED);
        list.Add(EventsEnum.USER_ADDED);
        list.Add(EventsEnum.USER_UPDATED);
        return list;
    }

    public override void HandleNotification(INotification notification)
    {
     
        switch(notification.Name)
        {
     
            case EventsEnum.USER_DELETED:
                View.Deselect();
                View.LoadUsers(userProxy.Users);
                break;
            case EventsEnum.CANCEL_SELECTED:
                View.Deselect();
                break;
            case EventsEnum.USER_ADDED:
                View.Deselect();
                View.LoadUsers(userProxy.Users);
                break;
            case EventsEnum.USER_UPDATED:
                View.Deselect();
                View.LoadUsers(userProxy.Users);
                break;
        }
    }
}

在官方文档中,Mediator实例可以持有Proxy实例。这里UserListMediator实例持有了UserProxy实例,再看看构造函数

public UserListMediator(UserList userList)
            : base(NAME, userList)
    {
     
        Debug.Log("UserListMediator()");
        userList.NewUser += userList_NewUser;
        userList.DeleteUser += userList_DeleteUser;
        userList.SelectUser += userList_SelectUser;
    }

写法有些像Proxy,也是pureMVC的惯例写法,第二个参数是这个UserListMediator的视图类,会在父类Mediator里面用object类型的变量持有,所有继承自Mediator的类都应该持有属于其的视图类的实例,视图类实例只负责持有操作视图的相关方法,没有任何关于视图响应时应该处理的功能逻辑的操作 。视图类可以是任何类型的变量,这里是UserList类型的。也因为这样,要获取Mediator的视图类要自己像下面代码一样写个get的方法,当然这个获取只能是私有的。

  private UserList View
    {
     
        get {
      return (UserList)ViewComponent; }
    }

现在来看第一个参数,因为每个proxy和mediator类的实例都是只有一个的。第一个NAME参数是用于实现通过ApplicationFacade得到这个UserListMediator的实例的标记,NAME 是个常量,可以通过类名而不是实例名找到它。
下面的代码可以得到UserProxy的实例

userProxy = Facade.RetrieveMediator(UserListMediator.NAME) as UserProxy;

这段代码经常出现在继承了Command类的类中,开发者用这种写法来直接获得Mediator,Command类一般会直接获得Proxy 和 Mediator直接进行操作。
在继承自Mediator类的实例注册的时候,会调用OnRegister方法,UserListMediator的OnRegister方法获取了UserProxy此外还将其数据加载到了UserList所持有的列表视图中

 public override void OnRegister()
    {
     
        Debug.Log("UserListMediator.OnRegister()");
        base.OnRegister();
        userProxy = Facade.RetrieveProxy(UserProxy.NAME) as UserProxy;
        View.LoadUsers(userProxy.Users);
    }

来看看 里面的ListNotificationInterests方法

 public override IList<string> ListNotificationInterests()
    {
     
        IList<string> list = new List<string>();
        list.Add(EventsEnum.USER_DELETED);
        list.Add(EventsEnum.CANCEL_SELECTED);
        list.Add(EventsEnum.USER_ADDED);
        list.Add(EventsEnum.USER_UPDATED);
        return list;
    }

这个方法也是重写Mediator的方法,这个方法主要是添加感兴趣的通知,返回的list会由PureMVC机制进行处理。

再看看 里面的 HandleNotification方法

public override void HandleNotification(INotification notification)
    {
     
        switch(notification.Name)
        {
     
            case EventsEnum.USER_DELETED:
                View.Deselect();
                View.LoadUsers(userProxy.Users);
                break;
            case EventsEnum.CANCEL_SELECTED:
                View.Deselect();
                break;
            case EventsEnum.USER_ADDED:
                View.Deselect();
                View.LoadUsers(userProxy.Users);
                break;
            case EventsEnum.USER_UPDATED:
                View.Deselect();
                View.LoadUsers(userProxy.Users);
                break;
        }
    }

这个方法对应的是ListNotificationInterests里面添加的通知,功能是如果监听的通知发生了之后,这个函数就会调用,并且对对应的监听执行对应的操作。 举个简单的例子说明其作用:在调用

  SendNotification(EventsEnum.USER_UPDATED);

的时候会执行这个HandleNotification方法的case EventsEnum.USER_UPDATED分支里面的操作。
官方文档不建议一个HandleNotification处理的case超过5个

再看看UserListMediator的构造函数

     userList.NewUser += userList_NewUser;
        userList.DeleteUser += userList_DeleteUser;
        userList.SelectUser += userList_SelectUser;

里面的这段代码用了代理的写法,分别对应了UserList视图的点击增加用户按钮的回调,点击删除用户按钮的回调和选中某项的回调,在PureMVC中,视图和其对应的Mediator使用的交互方式一般是:视图开放代理,Mediator去注册对应的代理

看到这里 就不详细介绍 UserList UserForm 以及 UserFormMediator了,因为前两个只是简单的视图组件,后一个结构和UserListMediator大同小异,介绍UserListMediator已经足够了解Mediator了,接下来通过一个UI响应看看Mediator之间是怎么交互的,
userList_NewUser是UserListMediator对 UserList点击了新建用户的按钮的注册,来看看userList_NewUser里面做了什么

  void userList_NewUser()
    {
     
        UserVO user = new UserVO();
        SendNotification(EventsEnum.NEW_USER, user);
    }

发送了一个通知,将UserVo作为通知的参数。
在Visual Studio中光标定位在EventsEnum.NEW_USER的NEW_USER上面,按下Shift+F12,看看哪个地方对这个进行了监听,如图所示:
在这里插入图片描述
可以发现在UserFormMediator里面对这个通知进行了监听,看看监听处理的内容,

 public override void HandleNotification(INotification note)
    {
     
        Debug.Log("UserFormMediator HandleNotification ");

        UserVO user;
        switch (note.Name)
        {
     
            case EventsEnum.NEW_USER:
                user = (UserVO)note.Body;
                View.ShowUser(user, UserFormMode.ADD);
                break;
                ...

它通过获取note.Body这个参数并强转成UserVO类型来获得SendNotification时候的传的user参数,这是惯例写法,在获得user之后,UserFormMediator将其传给了自己的视图UserForm,并让其将这个user显示出来。
其他的点击事件或者数据更改的运行流程也都是这样。这里就不再详述。

这里再额外说一下MacroCommand。
MacroCommand 让你可以顺序执行多个 Command。每个执行都会创建
一个 Command 对象。
MacroCommand 在构造方法调用自身的 initializeMacroCommand方法。需重写这个方法,调用 addSubCommand 添加子 Command。在这个方法中任意组合 SimpleCommand 和 MacroCommand 来达到目的。MacroCommand 执行的时候会按添加的顺序执行子Command并将唤起时候的Notification参数传进去

正确使用框架需要注意的事项以及写法

  • 对于同一个通知,
    每个Mediator对其注册的顺序也就是每个Mediator的ListNotificationInterests调用顺序,决定了通知发生时,Mediator对通知的响应顺序。 可以理解为对通知的响应顺序是Mediator的初始化和注册的顺序决定的

    Facade.RegisterMediator(new UserListMediator(mainUI.userList)); 
    Facade.RegisterMediator(new  UserFormMediator(mainUI.userForm));  
    
  • Mediator类的对象需要持有其需要的Proxy类的对象以便直接更改数据

  • Mediator处理内容包括直接调用proxy的某个方法来通知proxy更改数据,通过Command来进行与其他Mediator的交互,对Notification进行监听来进行与自己相关的视图逻辑处理,直接改变对应视图的状态,发送Notification

  • Command 类是无状态的,只在需要时才被创建。在执行完Execute方法后便会被销毁,所以变量一般不引用Command的实例

  • Command 可以获取 Proxy 对象并与之交互,发送 Notification,执行其他的 Command。经常用于复杂的或系统范围的操作

  • 当用 View 注册 Mediator 时,Mediator 的 listNotifications 方法会被调用,
    以数组形式返回该 Mediator 对象所关心的所有 Notification。之后,当系统其它角色发出同名的 Notification(通知)时,关心这个通知的Mediator 都会调用 handleNotification 方法并将 Notification 以参数传递到方法。

  • Proxy发送,但不接收Notification,如果让 Proxy 也侦听 Notification(通知)会导致它和 View(视图)层、Controller(控制)层的耦合度太高。

  • 一般地,实际的应用程序都有一个 Façade 子类,这个 Façade 类对象负责先初始化所有的Proxy,然后初始化并注册所有的Command,其中一个Command是用来来注册所有的Mediator。

  • PureMVC的惯例的启动写法是:Facade 子类应该写有一个StartUp方法,这个StartUp方法一般是调用注册所有Mediator的Command,应用程序调用 Facade 子类的 Startup 方法,并传递应用程序自身的一个引用来让这个Command能注册所有的Mediator,这样做使得应用程序不需要过多了解 PureMVC,这种写法一般要求这个引用持有所有的视图组件类以便Mediator进行初始化。

  • 继承自Facade的类的单例应该保存在Facade的m_instance变量里面,这样继承自Notifier的类才能通过Facade变量引用到,这时PureMVC框架内部写好的。

  • Notification使用到的通知字符串最好是由字符串常量负责,这些字符串常量集中定义在一个类里面,使用字符串常量避免了临时写字符串带来的错误风险,这种错误出现了排查起来通常需要较久的时间。

  • Mediator只负责对自己持有的视图的操作逻辑 简单的更改Proxy的逻辑以及监听Notification和在合适的时候发送Notification,Mediator可以保存一个或多个视图类,其对应的视图类只负责给他的Mediator提供视图响应时候的数据和接口以及更改视图显示的接口。

  • Mediator持有的视图类不能过大也不能过小,可以把一组相关的视图组件放在一个视图类里,不应该让Mediator的handleNotification方法负责复杂逻辑。业务逻辑应该放在 Command 中而非在 Mediator 中,一般一个 Mediator(handleNotification 方法)处理的Notification 应该在 4、5 个之内。

  • Mediator 的职责应该要细分。如果处理的 Notification 很多,则意味着 Mediator 需要被拆分,在拆分后的子模块的 Mediator 里处理要比全部放在一起更好。

  • Mediator 对外不应该公布操作视图的函数。而是自己接收Notification 做出响应来实现。

  • Proxy只负责对自己持有的数据的操作逻辑以及操作过后的发送Notification的逻辑,其对应的数据类只负责持有数据,没有任何对数据的操作以及其他操作。

  • 在开发中,应尽量做到Mediator 依赖于 Proxy,而 Proxy却不依赖于 Mediator 。Mediator 必须知道 Proxy 的数据是什么,但 Proxy 却并不需要知道 Mediator 的任何内容。

  • 在PureMVC中,只有Mediator与其对应的视图类,Proxy与其对应的数据类能够用紧密耦合的写法之外,其他的地方不应出现紧密耦合的写法,例如一个Mediator里面持有另外一个Mediator的引用,如果一个 Mediator 要和其他 Mediator 通信,那它应该发送 Notification 来实现,而不是直接引用这个 Mediator 来操作,更复杂的涉及到多个Mediator或者Proxy的逻辑应该放置在Command里面进行。

  • 在Command中,允许注册、删除 Mediator、Proxy 和 Command,或者检查它们是否已经注册;发送 Notification 通知 Command 或 Mediator 做出响应;获取任意多个 Proxy 和 Mediator 对象并直接操作它们。Mediator 和 Proxy 可以提供一些操作接口让 Command 调用来管理 ViewComponent 和 Data Object,同时对 Command 隐藏具体操作的细节。

  • Proxy 是有状态的,当状态发生变化时发送 Notification 通知Mediator,将数据的变化反映到视图,因Proxy状态发生变化而发送Notification的操作不应该放置在更改状态的Mediator或者Command。

  • 如果一个 Mediator 有太多的对 Proxy 及其数据的操作,那么,应该把这些代码重构在 Command 内,简化 Mediator,把业务逻辑(Business Logic)移放到Command 上,这样 Command 可以被 View 的其他部分重用,还会实现 Mediator 和 Proxy 之间的松耦合提高扩展性。

  • Proxy 不监听 Notification,Proxy 并不关心 View的状态。Proxy 提供方法和属性让其它角色更新数据。Proxy 对象不应该通过引用、操作 Mediator 对象来通知系统它的 Data Object(数据对象)发生了改变。 而是通过发送Notification的方法。Proxy 不关心这些 Notification 被发出后会影响到系统
    的什么。这样,把 Model 层和系统操作隔离开来,这样当 View 层和 Controller 层被重构时就不会影响到 Model 层,但是Model 层中的改变总会造成 View/Controller 层的一些重构。

  • 关于数据的操作逻辑尽可能放在 Proxy 中实现,这样当两个Mediator需要同一个Proxy的某个对数据操作的时候,Proxy都可以提供给他们,如果放置Mediator就不一样了。

框架的优点

PureMVC优缺点

  • 1.利用中介者,代理者实现解耦,使得Model、View、Controller之间耦合性降低,提升了部分代码的重用
  • 2.View界面可以实现重用
  • 3.Model数据可以实现重用
  • 3.代码冗余量大,对于简单的功能都得创建View、Mediator、Command、Facade,Proxy,Model脚本
  • 4.操作过程比较繁琐的流程,Mediator中的代码会显得流程较为复杂难懂,除非你很熟悉PureMVC执行原理

PureMVC特点

  • 1.通知的传递要经过强转操作,如果通知的内容是值类型,则涉及装箱和拆箱的操作
  • 2.命令/通知是以观察者模式实现,命令/通知在观察者中利用反射获取方法并执行
  • 3.没有Service(可按照MVC的构造,自行添加与网络通讯的这个模块)
  • 4.数据通过通知传递,SendNotification只有一个object类型参数,会感觉数据传输受限,可以将数据组合成一个类型/结构传递,或者是为Notification再拓展一个参数。

参考链接:
解读PureMVC框架
PureMVC框架解读(上)

你可能感兴趣的:(框架)