PureMVCS是将PureMVC部分功能优化,并将PureMVC中Model模块拆分为Model和Service两个模块,重新划分了View-Controller-Model-Service之间的交互权限的轻量MVCS框架。
如果你对PureMVC框架不熟悉,你一定要动手去用PureMVC框架写一个案例,你可以查看我的过往文章:
PureMVC框架最根本的功能还是解决对象与对象之间因数据交互引起的引用错乱(A类型引用B类型,B类型引用C类型,或者是新手容易将所有类型都设置为单例类),PureMVC框架用事件通知机制合理的解决了数据在不同类型之间传递时类型互相引用的问题(A不再需要引用B类型,但A类型却可以使用B类型中的数据)。
标准版C#版PureMVC框架中有多处设计不合理之处,比如:事件通知调用反射机制,视图模块中反复的拆装箱操作导致框架效率低下,让新程序员觉得PureMVC框架是一个效率很差的框架而放弃使用。但是其实PureMVC本身设计的缺陷很多时候都可以利用的别的设计方式来改进,使其在项目中得以使用。
下面我们细数一下被大家广为诟病的几个缺陷:
注意:PureMVC Github官网PureMVC/puremvc-csharp-standard-framework在2017年4月份已经优化了因反射调用造成的效率问题,博主在今日查找时才发现,不过还是写在这里供大家学习。点击查看旧版PureMVC。如果你的项目中有使用反射或者打算使用,推荐《为什么反射效率那么低?》,或许对你有所帮助!
以下是在Unity中测试的数据以及代码:
public class TestClass
{
public int sum;
public void TestFunc(int i,int j)
{
sum = i + j;
}
}
public class ReflectionTest : MonoBehaviour
{
private void Start()
{
int a = 10;
int b = 5;
// 1.普通调用
TestClass test = new TestClass();
DateTime start = DateTime.Now;
for (int i = 0; i < 1000000; i++)
{
test.TestFunc(a, b);
}
var offset = DateTime.Now - start;
Debug.Log("直接调用1000000次耗时:"+offset.TotalSeconds);
// 2.普通反射调用
start = DateTime.Now;
for (int i = 0; i < 1000000; i++)
{
Type type = test.GetType();
var method = type.GetMethod("TestFunc");
method.Invoke(test, new object[] { a, b });
}
offset = DateTime.Now - start;
Debug.Log("反射调用1000000次耗时:" + offset.TotalSeconds);
// 3.利用委托封装调用
start = DateTime.Now;
Action<int, int> function = test.TestFunc;
for (int i = 0; i < 1000000; i++)
{
function(a, b);
}
offset = DateTime.Now - start;
Debug.Log("委托调用1000000次耗时:" + offset.TotalSeconds);
}
}
上述案例测试案例中已经展示了如何利用委托的方式来避免使用反射调用。将需要通过反射调用的方法利用委托的方式记录在Observers中(比如需要调用Mediator中的HandleNotification方法,在注册Mediator时将Mediator的HandleNotification方法映射到IObserver接口中的委托字段,同时插入到Observers对象的字典中,当Observers接收到事件通知时,查找字典并执行委托即可)。具体优化步骤如下:
详细设计见框架源码。
以下是在Unity中测试的数据以及代码:
private void Start()
{
int sum = 0;
// 无拆装箱
var start = DateTime.Now;
for (int m = 0; m < 1000000; m++)
sum += m;
var offset = DateTime.Now - start;
Debug.Log("无拆装箱1000000次耗时:" + offset.TotalSeconds);
// 拆装箱
start = DateTime.Now;
object data = 0;
for (int m = 0; m < 1000000; m++)
{
// 拆箱
int intData = (int)data;
intData += m;
// 装箱
data = intData;
}
offset = DateTime.Now - start;
Debug.Log("拆装箱1000000次耗时:" + offset.TotalSeconds);
}
PureMVC中Mediator会发生拆装箱的根本原因是因为Mediator子类没有统一实现接口,而是直接用object记录的View的,因此每次Mediator调用View都会引起一次拆装箱,如果我们让子类视图都实现IBaseView(IBaseView定义View中最常用的方法),这丫样就减少了每次调用视图引起的拆装箱操作也某种程度上帮住你分离了视图逻辑,减轻视图的代码量。
IBaseView中定义视图中最常用的方法,定义之外的方法还是只能通过拆箱为对应类型进行访问。
public interface IBaseView
{
E_ViewType ViewName { get; set; }
IMediator Mediator { get; set; }
void Initialize();
void Open();
void Close();
void OpenAnimation();
void CloseAnimation();
}
Mediator中添加要给IBaseView字段,Mediator子类即可直接调用View中提供的方法,避免了拆装箱的操作。
public class Mediator
{
// 用此种方式合理规避拆装箱
public IBaseView View { get; set; }
public Mediator(string mediatorName, IBaseView view)
{
this.View = view;
this.View.Mediator = this;
}
}
在PureMVC框架中,Model负责数据操作和远端数据操作,但是在多个项目的经验使用之后,Model模块会发生代码过于冗长的现象,而且每个项目使用的网络传输协议有不太相同,因此在PureMVCS框架中我将Model中数据处理和服务器交互逻辑的分离成了Model模块和Service模块。Model模块只处理数据的增删改查,Service模块负责与服务器进行交互,负责将Model的数据上传服务器或是将服务器数据下载发送到Model中。
如上文中PureMVCS概念图中的流程所示,Service模块只与Model模块有数据来往,避免Service模块与其他模块的耦合有利于在不需要Service模块的项目中将Service模块剔除掉(比如数据存储在本地或不需要与服务器交互的项目)。从图中也可以看出从View到Service的虚线,这代表View可直接向服务器发起事件通知,而无须通过Controller->Model->Handler冗长的传递方式,虽然View直接向Service模块发起事件通知并不会引起什么框架的耦合或是不稳定,但是如果能够严格按照这种规范编码的话,也可以让别的程序员更加容易接手该项目。
上文我们已经解释过Service/Handler在 PureMVCS(LuaMVC)中到底扮演什么样的角色已经它应该完成什么样的功能,简单总结为以下几点:
Service/Handler模块设计与View/Mediator模块设计较为相似,只是取消了发送事件通知的功能,因此Handler类无需继承Notifier(Notifier提供了发送通知的功能)。
为了便捷Handler子类与服务器的访问,在Handler类中持有Socket对象(LuaMVC中提供的PomeloClient,是相同的概念,都是通过IP地址端口号与服务器建立连接的对象),每个Handler子类都可以在框架初始化完成之后方便的通过设计的接口进行数据交互,无需考虑与服务器建立连接的过程。
Service类记录所有已经注册的Handler对象,负责将注册的Handler对象注册到观察者类中以持续监听事件通知。Service类需实现单例以方便PureMVC其他模块进行调用。
public interface IService
{
void RegisterHandler(IHandler handler);
IHandler RemoveHandler(string handlerName);
IHandler RetrieveHandler(string handlerName);
bool HasHandler(string handlerName);
void RegisterObserver(string notificationName, IObserver observer);
void RemoveObserver(string notificationName, IObserver observer);
}
public class Service : IService
{
private IDictionary<string, IHandler> m_handlers =
new Dictionary<string, IHandler>();
}
Handler类持有与服务器建立连接的Socket对象,LuaMVC中采用的是PomeloClient(网易开源服务器框架),Handler类需要标记为LuaCallCSharp,方便Lua代码也可以直接访问Sokket对象。
public interface IHandler
{
string HandlerName { get; }
IList<string> HandleNotification();
void Request(INotification notification);
void OnRegister();
void OnRemove();
}
[LuaCallCSharp]
public class Handler : IHandler
{
private static PomeloClient m_pomeloClient = null;
public static PomeloClient pomeloClient
{
get
{
if( null == m_pomeloClient )
m_pomeloClient = new PomeloClient("127.0.0.1", 3344);
return m_pomeloClient;
}
set { m_pomeloClient = value; }
}
}
PureMVCS相比PureMVC框架修复了两个原有设计缺陷以及新增了Service/Handler模块,使得PureMVCS在效率和在面对需要连接服务器的项目来说有了更强的适应性。
View(视图)可直接通知Handler是把双刃剑,可使项目开发更加便捷,但是也可能导致项目代码理解加难,合理利用最为重要。
PureMVC利用事件通知的机制实现模块直接的解耦,但此也造成了PureMVC最大的弊端。随着项目的扩大,项目中事件的名称也随之增多,使得项目有所混乱。无法一条线定位数据的流向也成了一大困惑,出现Bug时新手可能会难以定位问题所在,变相加长bug修复周期。
以下是针对上述几个弊端提出的合理意见:
源码 : https://github.com/ll4080333/luaMVC
如果对你有用,记得点一波Star,关注博客哦。
LuaMVC是我在项目种的经验总结,如果恰巧你也需要这样的框架来快速开发,那你可以期待后续的更新哦。
如果你有什么更好的意见与建议欢迎加留言或者加群:LuaMVC/SpringGUI交流群 593906968 。