PureMVC框架的学习笔记

曾经自己做的项目UI层使用简单的UI框架,发现扩展性不强,大型项目多人协同也是个问题,于是学习了比较经典的基于MVC的PureMVC以及基于MVVM的LoxodonFramework,今天记录一下有关PureMVC的学习。

MVC思想

什么是MVC?

MVC的全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,是一种软件设计典范。它是用一种业务逻辑、数据与界面显示分离的方法来组织代码,将众多的业务逻辑聚集到一个部件里面,在需要改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑,达到减少编码的时间。

MVC开始是存在于桌面程序中的,M是指业务模型,V是指用户界面,C则是控制器。

使用的MVC的目的:在于将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。通过Controller实现Model与View层的解耦,同理MVVM、MVP也是通过VM、P来使Model与View层解耦。采用这种思想的好处是降低了M与V的耦合,方便扩展、便于重用与管理。

clip_image006

图:MVC结构图(实线——>表示依赖;虚线——>表示事件、通知等)

MVC的核心思想是:代码重用(code reusability)、关注点分离(separation of concerns)

模型(Model):数据模型用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法。Model有对数据直接访问的权利,例如对数据库的访问。“”Model“”不依赖“VIew”和“Controller”,模型中的数据一般通过一种刷新的机制发布,为了实现这种机制,那些需要监视此Model的View必须先在此Model上注册,来达到监听数据模型的改变。(观察者模式"发布订阅")

视图(View):视图层能够实现数据有目的的显示(非必须功能)。在View中一般没有程序上的逻辑(可以拥有单纯显示的接口)。为了实现View上的刷新功能,View需要访问它监视的数据模型(Model),因此应该先在被View监视的数据那里注册。

控制器(Controller):控制器起到不同层面上的组织作用,用于控制应用程序的流程。它处理事件并作出相应。“事件”包括用户的行为和数据模型上的改变。

简易UI框架

1、UI框架核心方法

  • BaseUI会记录UI的名字和Controller用来将UI与具体操作方法解耦
  • UIManager利用字典记录所有的UI组件,然后提供一些打开关闭UI的方法
  • EventManager(消息系统)来实现不同模块之间的交互,消息系统一般采用观察者模式利用一个关键字注册(订阅)多个方法,然后在利用这个关键字通知已经注册的方法。

2、UI框架拓展方法

  • ResourceManager(资源管理者),封装一些加载Resources资源的方法,同步加载,异步加载,加载并实例化等方法。
  • DataHelper(数据解析器),对XML,Json、Protobuf等数据格式序列化与反序列化插件进一步封装。
  • Singleton单例模式
  • Common Extension(公共拓展组件)利用泛型和拓展方法对Unity中的方法进行进一步的封装,加快开发速度。

3、简易UI框架的优缺点

  • 容易上手使用
  • 针对C#新手相对容易理解
  • 只适合很小的项目
  • 扩展性很差
  • 逻辑会堆积在MonoBehaviour脚本下,效率降低

躺过上面这种简易UI框架的坑之后,才意识到一个大一点的项目,或者是多人合作开放的项目,一定要有一个较为牢靠的框架作为基础,在Unity中有几种常见的框架,也是经典的框架拓展过来的。如PureMVC,LoxodonFramework等等

PureMVC框架

PureMVC优缺点:

  1. 利用中介者、代理者、命令模式实现解耦,使用Model、View、Controller直接耦合降低的更为彻底,提高了部分代码重用性
  2. View界面与Model数据皆可以实现重用
  3. 代码冗余量太,对于简单的功能也需要创建对应的View、Mediator、Command、Facade、Proxy、Model等脚本
  4. 操作过程比较繁琐,Mediator中的代码显得流程较为复杂难懂
  5. 消息通过反射的形式比较耗费性能(新版本已经使用委托)

PureMVC特点:

  1. 通知的传递都需要经过装箱拆箱操作(新版本已经采用泛型委托)
  2. 命令/通知是以观察者模式实现,在观察者中利用反射获取方法并执行(新版本已修改为委托)
  3. 没有Service模块需要自行添加
  4. 和数据通过通知传递,SendNotification只有一个object类型参数,会感觉数据传输首先,可以将数据组合为一个类来传递,只需要对类进行约束。

PureMVC Core Scripts //核心文件

1.View.cs : IView.cs

  1. 字典mediatorMap记录已注册的中介者(key是中介者名字,value是中介者接口)
  2. observerMap字典记录已注册的观察者(key是事件名称,value是观察者接口)
  3. 提供注册/注销中介者和观察者的方法(Register、Remove)
  4. 核心方法通知观察者的方法NotifyObervers
  5. 实现View单例

IView.cs接口规了View要实现的方法
2.Controller.cs : IController.cs

  1. 字典commandMap记录已注册的命令(key是命令的名称,value是Func<ICommand>)
  2. 记录IView接口,以此来执行View中的通知,IView在构造Controller时赋值的
  3. 实现Controller单例
  4. 提供注册、注销、执行命令的接口(Register,Remove,Excute)
  5. IController.cs接口规了View要实现的方法

3.Model.cs :IModel.cs

  1. 字典proxyMap记录已注册的Model代理(Proxy)(key是代理名称,value是代理类型接口)
  2. 实现IModel的方法,包括注册、注销、获取代理的方法
  3. 实现Model单例
  4. IModel.cs接口规了Model要实现的方法

GameObject : 基本上是最底层(UI视图),除了Unity自身的组件外,不依赖PureMVC任何类。

Mediator : 只依赖GameObject,与GameObject一对一绑定,GameObject的输入通过事件(event)传递到Mediator调用。

Command :  最底层,通过消息系统与Mediator通信,依赖Proxy(因为有时需要通过Proxy得到实体进行一些运算,Proxy只进行一些数据操作而已)。

Proxy : 最底层,除了对应的数据类之外,不依赖任何类。

View : 单例,除了内嵌了一个观察者模式的消息系统外,它的职责只有管理Mediator(Register,Remove,Retrieve,Has)

Controller : 单例,在内部调用了消息系统的监听方法,它的职责只管理Command(Register,Remove,Execute,Has)

Model : 单例,职责只管理Proxy(Register,Remove,Retrieve,Has)

Facade : 单例,职责管理View,Controller,Model,因为V iew有消息系统,所以它间接也有发送消息的功能

Notifier : 功能类,调用了Facade的消息系统来通知其他对象,所以实质上是在调用View的消息系统(Observer)发送消息

从这些类的功能上可以看出,每个类的职责都很单一,功能之间分离得很明显。Controller是利用了对应Type的类型来创建对象,从而调用方法,每次执行一个命令都要实例化一个类。Observer消息系统利用了反射对象的方法名(GetMethod)来调用观察对象的方法,新版本已经使用泛型委托来解决。

                                图: PureMVC设计示意图(摘自官方网站

从设计图中可以清楚看到,除了核心层Facade之外,还有Mediator,Proxy,Command。

PureMVC框架的目标很明确,它把程序分为了低耦合的三层:Model、View、Controller。由Facade统一管理。

  1. Model保存对Proxy对象的引用,Proxy负责操作数据模型,与远程服务通信存取数据。
  2. View保存对Mediator对象的引用。由Mediator对象来操作具体的视图组件“ViewComponent”
  3. Controller保存所有Command的映射。Command可以获取Proxy对象并与之交互,通过发送Notifacation来执行其他的Command。(本质仍是View内部的观察系统)
  4. Proxy是负责操作数据模型的,什么是数据模型?数据模型就是数据库,XML等。我们可以直观地理解为Proxy是用来对数据模型进行查询、插入、更新、删除等操作的类。操作完成后就会发送Notification消息,通知其他层进行更新。Proxy只管发送信息,不会监听信息避免耦合。
  5. Mediator负责操作具体的视图组件,包括:添加事件监听器,发送或接受Notification,直接改变视图组件的状态。比如:给Button添加事件,点击Button时发送Notifacation,告诉Controller执行相应的操作。比如一个登录的按钮,Mediator会发送通知给controller,告诉它执行登录操作。Mediator还负责直接改变视图的状态。比如点击登录按钮之后,Mediator改变登录按钮置灰避免重复操作。还可以在视图上显示一条信息告诉我正在执行登录操作等。总结Medoator是用来管理具体视图的。
  6. Command可以获取Proxy对象并与之交互,通过发送Notification来执行其他的Command。比如点击登录按钮后,Mediator就会告诉Controller执行相应的Command。既然是登录需要知道用户的信息,Command就会告诉Proxy来查询数据库或者其他数据模型来访问相应数据。Proxy查询好数据后发送Notification通知Command我已经查询好了,然后把信息返回给Command进行验证,同时Mediator也可以接收Proxy发送的Notifacation,通过视图告诉用户正在验证信息。Command验证了用户信息后,发送Notification把验证结果返回给Mediator,告诉用户验证的结果。或者Command也可以发送Notifacation给执行其他Command操作,比如验证读取用户信息详细资料等。

PureMVC设计模式

代理模式:为其他对象提供一种代理以控制这个对象的访问。

使用:在PureMVC中的使用代理模式隔离了数据与其他系统的直接交互,都通过数据的代理类来进行操作,因为代理类又是继承至Notifier的所以Proxy也可以以通过Notifier.facade访问到View和Controller,有效隔离了数据类和其他类的耦合,使数据类的复用性提升。这样的数据类中的数据可以由策划来用工具生成,使开发更加的方便。

外观模式:为子系统的一组接口提供一个一直的界面,此模式定义了一个高层接口,使子系统更加容易使用,完美体现依赖倒置和迪米特法则。使用外观模式可以使子系统之间的依赖降低。可以为旧的子系统设立一个Facade类,然后用Facade类去与新的系统交互,降低旧系统对信息系统的依赖和产生复杂关系。

使用:在PureMVC中利用外观模式编写的Facade类是整个MVC框架对外的主要接口,在Facade类中记录了View、Model、Controller并且实现了单例,它几乎拥有MVC核心类中所有的对外接口,是一个典型的高层接口。

观察者模式:是一种一对多的依赖关系,让多个观察者对象同时监听一个主题对象,这个主题对象发生变化时,会通知所有观察者对象,使他们能够自动更新自己。

使用:在PureMVC框架中,利用观察者模式,在中介者类的构造函数中给中介者注册对应UI事件之后,当UI出发事件时,中介者会通过View再把通知发送给观察者,View中记录了每一个事件对应的观察者的字典,对观察者进行遍历然后执行通知中的具体事件。通知的事件在构造中介者时,通过重写ListNotificationInterests方法来添加。PureMVC中命令模式实现也是通过发送通知,让观察者执行通知调用Controller中的ExcuteCommand来触发的命令。

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

中介者的优缺点都很明显:有点事减少了各个具体类之间的耦合,使得可以独立地改变和复用各个类型。缺点是:中介者的控制会随着逻辑的复杂而更加复杂,这会使得中介者承担过多的任务。

使用:在PureMVC框架中利用中介者模式有效隔离了View层与Controller和Model层的耦合,View通过持有的数据代理,就可以有效的执行操作和数据处理。

命令模式:将一个请求封装为一个对象(即我们创建的Command对象),从而可以用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。

抽象命令Command:定义命令的接口,声明执行的方法。

具体命令ConcreteCommand:具体命令实现要执行的方法,它通常是虚的实现,通常会有接受者(Receiver),真正执行命令的对象。任何类都有可能成为一个接受者,只要能实现命令要求实现的相应功能 。

调用者Invoker:要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。

客户端Client:命令由客户端来创建,并设置命令的接受者。

使用:SimpleCommand和marcoCommand继承ICommand接口,实现Execute方法。在代码中调用SendNotifacation,然后通知会在View.NotifyObserver()方法中通知观察者,观察者执行Controller中的ExecuteCommand,ExecuteCommand中又会从字典中获取命令接口,调用Execute执行重写的命令,实现解耦。

单例模式:单例模式是最常用的设计模式,顾名思义一个类只有一个实例。

单例模式提供一个全局的访问点,使程序的开发变得更加的灵活,但是单例类的增多也会导致代码过度耦合,降低复用和维护。

使用:在PureMVC中,Facade、View、Model、Controller都实现了单例模式,使得批次访问更加的方便,并加以volatile关键词(volatile多用于多线程的环境,当一个变量定义为volatile时,读取这个变量的值时候每次都是从momery里面读取而不是从cache读。这样做是为了保证读取该变量的信息都是最新的,而无论其他线程如何更新这个变量),提供互相调用的接口。
 

Facade外观设计模式:在开发的过程中,我要在Controller中获取View以及Model的对象,修改Model,更新View或者是在View中获取Model,进行初始化操作,在业务逻辑很多的情况下,Model、View、Controller之间的频繁调用就会非常多,耦合性就会增强,在PureMVC中,通过一个上层的接口来负责所有核心层(Model、View、Controller)的管理和操作,在PureMVC中,使用了Facade设计模式。

外观设计模式定义:“为一组子系统或者是接口提供一个统一的界面,以简化其复杂度,降低耦合性。”

Facade类应被当成抽象类, 永远不被直接实例化,你应该具体编写 Facade 的子类,添加或重写 Facade 的方法来实现具体的应用。这个类命名为“ApplicationFacade”(当然,命名随你喜欢)。

如前所述,它主要负责访问和通知 Model、View 和 Controller 。即管理这三者。

Facade 是 Model、View 和 Controller 三者的“经纪人”。实际编写代码时你并不用导入这三者的类文件,也不用直接使用它们。Facade 类已经在构造方法包含了对核心 MVC 三者单例的构造。

Facade类在构造方法中初始化了 Model、View 和Controller 对象,并把对它们的引用保存在成员变量。

这样,Facade就可以访问 Model、View 和 Controller 了。这样把对核心层的操作都集中在 Facade,避免开发者直接操作核心层。

初始化facade

一般地,实际的应用都有一个facade子类,这个facade类对象负责初始化Proxy,Mediator和Command,建立Command与Notifacation名之间的映射,或者通过执行一个Command来注册所有的Proxy和Mediator。

这时候就提到了Command,Proxy和Mediator,他们的对应关系其实就是:

Proxy=>Model

Mediator=>View

Command=>Controller

什么是Model和Proxy?

从上面的截图可以看到Model和Proxy的关系图。

Model即数据(Data Object),游戏是基于数据驱动的,比如角色的数据,包括HP、MP、金币、等级、技能等,在PureMVC中是通过Proxy来进行管理的。Proxy即代理设计模式,“为其他对象提供代理以控制该对象的访问”,代理在PureMVC中被用来控制和管理数据模型的。

“Data Object”是以任意结构存储数据的对象。也就是说我们不会直接与Model通信,对Model的增删改查都是通过Proxy来处理的。

关于Proxy代理模式,比如球星C罗他的Proxy代理就是C罗团队,有什么商业合作事宜均通过C罗团队进行接洽,这样,C罗就不需要一个人去面对来自四面八方的合作沟通成本(降低耦合性),同时,团队也可以帮助C罗处理很多的事务,不需要每件事儿都要经由C罗的过目(一定程度上隐藏了其内部的实现细节),从代码角度,也满足”改变一个不影响其它“,我对部分数据的修改,不应该影响到其它的数据。
我们继续看上面的示意图,看下Model的箭头,他只和Facade进行交互。上面提到过这,这是为了降低耦合性。

旁边的一众Obj即Model,对应着Proxy,但并不是一个Model对应一个Proxy,如果是这样,就太繁琐了,比如一个模块中,可能包括很多种不同的Model数据,你可以定义多个不同的Model,但可以通过一个Proxy进行管理,这样更方便。 

通常会以同步的方式取得或设置Model数据。Proxy可能会提供访问DataObject部分属性或方法的API,也可能直接提供DataObject的引用(但不建议这样做,我们应该保护Model,提供相应的接口来访问)。如果提供了更新DataObject的方法,那么在数据被修改时会发送一个Notification通知系统的其他部分。

这里Notification通知,其实就是观察者模式,当一个对象发生改变的时候,同时也需要有很多其它的对象要对此做出响应,这时候就要使用观察者模式了,发布-订阅的模式,比如我们订阅了某个微信公众号,公众号发表了一篇文章,所有订阅的用户都可以收到提醒,这在游戏中无处不在,当Model发生变化的时候,通知View组件进行更新。那么在View中,就会有相应的方法来处理Notification通知,并进行相应的逻辑处理。

Proxy只发送Notification通知(在数据改变的时候),他并不处理Notification通知,他并不关心View组件如何变化。

Proxy 对象不应该通过引用、操作 Mediator 对象来通知系统它的 DataObject(数据对象)发生了改变。

也就是说,Mediator可以获取Proxy,但Proxy中不应该获取Mediator,如果要通知View层进行更新,发送Notification通知即可。

(Proxy 不关心这些 Notification 被发出后会影响到系统的什么)

这样Proxy和Mediator之间只存在单向耦合。

Proxy中也包含了一定的逻辑处理的部分,我们把 Domain Logic(域逻辑)尽可能放在 Proxy 中实现,这样尽可能地做到 Model 层与相关联的 View 层、Controller 层的分离。

比如计算扣税的函数,如果你将他放在Mediator或是Command中实现,那么就相当于把这部分代码耦合了,比如你的View要重建,或是别的Command也要使用该扣税函数,那么这部分代码就无法得到复用,所以放在Proxy中是更为合适的。

关于数据类型的转换

因为 Model(Data Object) 通常是一个复杂的数据结构,我们经常需要引用它的一部分属性并将类型转化成我们需要的数据。

通过getter 和 setter 属性,它可以很好地帮助我们解决这种频繁的类型转换问题。

可能需要定义不同的多个类型 getter 来取得 Data Object 某部分的数据。

public ArrayCollection searchResultAC 

 get {return data as ArrayCollection;}

 }

这是官方文档的例子,但在移动端,还是建议缓存起来,避免每次都进行转换来消耗内存,引起GC。

什么是View 与 Mediator ?

View即视图组件,和Model层一样,也是通过”中介“来进行管理,View是由Mediator来操作具体的视图组件(View Component)。包括:添加事件监听器 ,发送或接收 Notification ,直接改变视图组件的状态。

Mediator(中介者设计模式):

“用一个中介对象来封装一系列的对象交互“(重交互, 强逻辑)

*View Component即UI上的各种控件,按钮,列表,滚动条等等。

这样做实现了把视图和控制它的逻辑分离开来。对于View中组件的定义和初始化都在Mediator中定义和实现,这样即使UI重建,也只是更改Mediator。

因为 Mediator 也会经常和 Proxy 交互,所以经常在 Mediator 的构造方法中取得Proxy 实例的引用并保存在 Mediator 的属性中,这样避免频繁的获取 Proxy 实例带来的性能开销。

这里可以看到,通常View和Mediator是一对一的关系,但有些View会相对复杂,有多个子UI组成,Mediator中也可以有多个View Component引用(同一功能的不同子UI)。

但如果Mediator过于庞大,就要进行拆分,在拆分后的子模块的 Mediator 里处理要比全部放在一起更好。这部分工作需要慢慢的重构。

转化 View Component 类型

(这部分和Model是一样的处理方式 )

这个 Mediator 子类会在构造函数中把它的 View Component 传参给父类,它会在内部赋值给一个 protect 属性:viewComponent,并传化为Object 类型

Mediator 被构造之后,你可以通过调用它的 setViewComponent 函来动态给它的 View Component 赋值(修改)。之后,每一次需要访问这个 Object 的 API 时,你都要手动把这个 Object转化成它的真正类型。这是一项烦琐重复的工作。

和上面的Model一样,Mediator中保存了View的引用,我们最缓存下来。

Mediator通常要做的事:

1.检查或修改 View Component 的属性

2.检查或修改 Proxy 对象公布的属性

3.发送一个或多个 Notification ,通知别的 Mediator 或Command 作出响应(甚至有可能发送给自身)。

但要注意一点,业务逻辑(Business Logic)应该放在Command中,而非Mediator中!

实际上Mediator并不处理复杂的逻辑。像Model那样,域逻辑的部分,可以放在Mediator中实现,减少与Controller的耦合性,也提高了重用性。

注意:不要将检测或是对VC(View Component以及Proxy)属性的修改当作是业务逻辑(Business Logic)

下面是一些有用的经验:

1.如果有多个的 Mediator 对同一个事件做出响应,那么应该发送一个 Notification,然后相关的 Mediator 做出各自的响应。(观察者模式)

(比如说,你当前屏幕上显示了3个UI,每个UI上都显示着玩家的金钱数量,当金钱发生变化的时候,Proxy应该发送一个相应的Notification通知,然后3个UI接受通知并进行View的更新)

2.如果一个 Mediator 需要和其他的 Mediator 进行大量的交互,那么一个好方法是利用 Command 把交互步骤定义在一个地方。

3.不应该让一个 Mediator 直接去获取调用其他的 Mediator,在Mediator 中定义这样的操作本身就是错误的。可以看上面的概念图,Mediator和Mediator之间不会直接进行通信的,这样就违背了降低耦合性的初衷。

当一个View的改变会影响到另外一个View组件,发送Notification通知即可。

4.Proxy 是有状态的,当状态发生变化时发送 Notification 通知Mediator,将数据的变化反映到视图组件上。

将这些多次使用到的“请求“,通过command实现,使之更加的独立,提高重用性。

Proxy设计模式 vs Mediator设计模式?

前面提到的两个设计模式,两者所做的事情非常的相似,但定义上,Proxy更侧重于控制数据的访问,相当于真实数据的代表,而Mediator则更侧重于数据的交互(封装了一系列对象的交互),强逻辑,比如AB之间交互的中间人,那么对于UI的交互是相对复杂繁琐的,所以使用Mediator来负责处理View上的操作。

在《大话设计模式》中举了个蛮不错的例子来说明Mediator,即联合国,类似的还有环境保护组织,我们日常能接触的房产中介,负责房屋的勘察,审核,买卖,缴税,过户等(交互,强逻辑)工作。

如果直接让我们和房东联系,很多不懂的知识外,还有法律上的风险。

另一个例子,4S店,我们买卖,售后等都要去4S店进行处理,在Mediator中,A和B进行交互,A和B都”认识“Mediator中介者,我们去找4S店,4S店负责和汽车的生产商沟通。

什么是Controller 与 Command ?

Controller保存了所有Command的映射,Command 类是无状态的,只在需要时才被创建。

这里使用到了Command命令设计模式,即将一个“请求”,"行为”封装为一个对象,将逻辑的部分进行独立封装,提高复用性,对View或Mediator的修改也不会影响到Command本身。通过Facade顶层接口,可以在Proxy,Mediator,Command之间,相互访问和通信。

Command 可以获取 Proxy 和Mediator对象并与之交互,发送 Notification,执行其他的 Command。经常用于复杂的或系统范围的操作,如应用程序的“启动”和“关闭”。应用程序的业务逻辑应该在这里实现。

Facade 需要在启动时初始化 Controller,建立 Notification 与 Command的映射。

Controller 会注册侦听每一个 Notification,当被通知到时,Controller 会实例化一个该 Notification 对应的 Command 类的对象。最后,将 Notification 作为参数传递给execute 方法。具体可以参考Command基类的实现。

也就是说,Command的执行是通过发送Notification通知操作的。

Command 对象是无状态的;只有在需要的时候( Controller 收到相应的Notification)才会被创建,并且在被执行(调用 execute 方法)之后就会被删除。所以不要在那些生命周期长的对象(long-living object)里引用 Command 对象。

在运行中,可以通过Command来初始化Proxy和Mediator,即注册到Facade中。

比如:

public class ModelPrepCommand : SimpleCommand {

 //由 MacroCommand 调用

 public override void Execute (INotification notification) {

 IFacade.registerProxy (new SearchProxy ());

 IFacade.registerProxy (new PrefsProxy ());

 IFacade.registerProxy (new UsersProxy ());

 }

(这里就不贴Command部分的截图上,大家能看得懂Model和Mediator后,Command这里就比较清晰了)

两种类型的Command:

Command 要实现 ICommand 接口。在 PureMVC 中有两个类实现了ICommand 接口:SimpleCommand、MacroCommand。SimpleCommand 只有一个 execute 方法,execute 方法接受一个Inotification 实例做为参数。实际应用中,你只需要重写这个方法就行了。

MacroCommand 让你可以顺序执行多个 Command。每个执行都会创建一个 Command 对象并传参一个对源 Notification 的引用。MacroCommand 在构造方法调用自身的 initializeMacroCommand 方法。实际应用中,你需重写这个方法,调用 addSubCommand 添加子 Command。你可以任意组合 SimpleCommand 和 MacroCommand 成为一个新的 Command。

其它要介绍的:

Business Logic(业务逻辑)和 Domain Logic(域逻辑)?
 

在程序的很多地方你都可以放置代码(Command,Mediator 和Proxy);不可避免地会不断碰到一个问题:哪些代码应该放在哪里?确切的说,Command 应该做什么?

程序中的逻辑分为 Business Logic(业务逻辑)和 Domain Logic(域逻辑),首先需要知道这两者之间的差别。

Business Logic(业务逻辑)要协调 Model 与视图状态(View)。

Model 通过使用 Proxy 来保证数据的完整性、一致性 。Proxy 集中程序的Domain Logic(域逻辑),并对外公布操作数据对象的 API。它封装了所有对数据模型的操作,不管数据是客户端还是服务器端的,对程序其他部分来说就是数据的访问是同步还是异步的。

Mediator 和 Proxy 可以提供一些操作接口让 Command 调用来管理 ViewComponent 和Model( Data Object),同时对 Command 隐藏具体操作的细节。

Observer 与 Notification ?

PureMVC的通信是使用观察者模式以一种松耦合的方式来实现的,几乎在游戏开发中,无处不在的设计模式,你只需要使用一个非常简单的方法从 Proxy,Mediator, Command 和 Facade 发送 Notification,甚至不需要创建一个Notification 实例。

Facade 和 Proxy 只能发送 Notification,Mediators 既可以发送也可以接收 Notification,Notification 被映射到 Command,同时 Command 也可以发送 Notification。这是一种“发布/订阅”机制 ,所有的观察者都可以收到相同的通知。例如多个书刊订阅者可以订阅同一份杂志,当杂志有新刊出版时,所有的订阅者都会被通知。

Facade 保存了 Command 与 Notification 之间的映射。当 Notification(通知)被发出时,对应的 Command(命令)就会自动地由 Controller 执行。Command 实现复杂的交互,降低 View 和 Model 之间的耦合性。

定义Notification常量

当这些 Notification 的名称常量需要被其他的程序访问时,我们可以使用单独的“ApplicationConstants”类来存放这些 Notification 名称常量定义。不管什么时候,都应该把 Notification(通知)名称定义为常量,需要引用一个 Notification 时就使用它的名称常量,这样做可以避免一些编译时无法发现的错误。因为编译器可以检查常量;而使用字符串,如果你手误输入错误的字符串,编译器也不法知道,也无从报错。

Mediator发送、声明、接收Notification

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

这里Mediator是被通知者,当Proxy数据进行改变时,Mediator接收到通知,并对UI进行更新。

Proxy发送,但不接收Notification

在很多场合下 Proxy 需要发送 Notification(通知),比如:Proxy 从远程服务接收到数据时,发送 Notification 告诉系统;或当 Proxy 的数据被更新时,发送 Notification 通知视图组件进行更新等等。

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

View 和 Controller 必须监听 Proxy 发送的 Notification,因为它们的职责是通过可视化的界面使用户能与 Proxy 持有的数据交互。不过对 View 层和 Controller 层的改变不应该影响到 Model 层。

最后是文档中View Component 和 Mediator 交互的小例子:

假如有一个含表单的 LoginPanel 组件。对应有一个 LoginPanelMediator,负责与 LoginPanel 交互并响应它的输入信息发送登录请求。

LoginPanel 和 LoginPanelMediator 之间的协作表现为:LoginPanel 在用户输入完信息要登录时发送一个 TRY_LOGIN 的事件,LoginPanelMediator 处理这个事件,处理方法是发送一个以组件包含的 LoginVO 为“报体”的 Notification(通知)。

这里省略LoginPanel.mxml的部分,视图的部分各不相同。

LoginPanel 组件包含有一个用用户表单输入新创建的 LoginVO 对象,当用户单击“Login”按钮时触发一个事件,接下来的事情由LoginPanelMediator 接管。

这样 View Component 的角色就是简单收集数据,收集完数据通知系统。可以完善的地方是只有当 username 和 password 都有内容时才让 login按钮可用(enable),这样可以避免恶意登录。

View Component 对外隐藏自己的内部实现,它由 Mediator 使用的整个API 包括:一个 TRY_LOGIN 事件,一个 LoginVO 属性和 Panel 的状态属性。

最后小结:
 

View和Model之间是单向依赖关系,View必须知道Model是什么,View也是基于Model的数据来显示视图上的内容。而Model并不在乎View上的内容。

Proxy和Mediator在职责上,均是代理,中介者的角色,负责与其它组件进行通信。而他们的注册都由

Facade来统一进行管理。

Proxy和Mediator中不应该包含大量的业务逻辑,业务逻辑部分应该放在Command中处理,对于数据本身的一些操作,应该放在Proxy和Mediator中。

虽然Mediator中可以访问任意的Proxy,并进行修改,但不建议这样做,由 Command 来做这些工作可以实现 View 和 Model 之间的松耦合。这样Command可以被View的其它部分重用。

 资料:

官方GitHub连接:https://github.com/PureMVC

PureMVC官方网站:www.puremvc.org

博客:PureMVC解析_Peter_Gao_的博客-CSDN博客_puremvc

博客:PureMVC框架在Unity中的应用(一)_Peter_Gao_的博客-CSDN博客

猜你喜欢

转载自blog.csdn.net/qq_40097668/article/details/123987708