MVC

思考并回答以下问题:

  • 所谓的管理就是维护字典,然后对字典进行增删查。怎么理解?
  • 为什么Model和View实现类都需要维护一个自己的名字?有什么好处?没有会怎么样?
  • 为什么Model和View字典都是用自带的Name字段作为key,而Controller字典是用事件名?
  • 为什么Controller字典的Value用Type类来存储?需要用到的时候再实例化,预先实例化浪费内存。但之前需要管理,所以用typeof()得到Type对象,用到的时候再通过反射进行实例化。怎么理解?
  • Activator.CreateInstance是干嘛用的?
  • 所有启动的地方都要写在Awake中。怎么理解?

MVC

MVC的设计每个都不一样,但关键是解耦合。而解耦合的过程中会使用到大量的设计模式。最常用的有单例模式、观察者模式、命令模式、工厂模式等。

实现代码

管理类

MVCManager.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace MVCCore
{
// MVC管理类
public class MVCManager
{
// 维护三层的字典,对这三个字典进行增删查

// 存储Model子类对象的字典,key是Model子类自维护的自身类名
// 名称--模型
// 私有
private static Dictionary<string, Model> Dic_Model = new Dictionary<string, Model>();

// 存储View子类对象的字典,key是View子类自维护的自身类名
// 名称--视图
private static Dictionary<string, View> Dic_View = new Dictionary<string, View>();

// 存储命令对象的字典,key是事件名
// 事件名--控制器类型
private static Dictionary<string, Type> Dic_Command = new Dictionary<string, Type>();

// 以下是注册相关-------------------------------

/// <summary>
/// 字典是string和Model实例,按理说注册方法应该传入两个参数
/// 但Model的name交予自己维护了,所以只传入一个Model实例
/// 然后得到name即可。
/// 相比较一下Model没有自己的NAME属性,留给客户端传入有什么优缺点?
/// 注册模型,Model需要自带Name属性
/// 给Controller调用
/// </summary>
/// <param name="model">Model子类</param>
public static void RegisterModel(Model model)
{
if (!Dic_Model.ContainsKey(model.NAME))
{
// 设置Value之前可能需要做其他操作,先设置key
Dic_Model.Add(model.NAME, null);
}

Dic_Model[model.NAME] = model;
}

/// <summary>
/// 注册视图
/// 给Controller调用
/// </summary>
/// <param name="view">View子类</param>
public static void RegisterView(View view)
{
if (!Dic_View.ContainsKey(view.NAME))
{
// 设置Value之前可能需要做其他操作,先设置key
Dic_View.Add(view.NAME, null);
}
// 注册视图的时候把它要关注的消息也写进自己维护的EventsObserver列表
view.RegisterEvents();

Dic_View[view.NAME] = view;
}

/// <summary>
/// 注册命令
/// 给Controller调用
/// </summary>
/// <param name="eventName">事件名</param>
/// <param name="commandType">事件名绑定(触发)的命令对象</param>
public static void RegisterCommand(string eventName, Type commandObj)
{
if (!Dic_Command.ContainsKey(eventName))
{
// 设置Value之前可能需要做其他操作,先设置key
Dic_Command.Add(eventName, null);
}

Dic_Command[eventName] = commandObj;
}

// 注册end-------------------------------

// 以下是获取相关-------------------------------

/// <summary>
/// 根据Model类子类类型,获取对应的子类对象
/// </summary>
/// <typeparam name="T">泛型</typeparam>
/// <returns>Model子类对象</returns>
public static T GetModelObj<T>() where T : Model
{
foreach (Model model in Dic_Model.Values)
{
if (model is T)
{
return (T)model;
}
}
return null;
}

/// <summary>
/// 根据Model类子类类型,获取对应的子类对象
/// </summary>
/// <typeparam name="T">泛型</typeparam>
/// <returns>View子类对象</returns>
public static T GetViewObj<T>() where T : View
{
foreach (View view in Dic_View.Values)
{
if (view is T)
{
return (T)view;
}
}
return null;
}

// 获取 end-------------------------------

/// <summary>
/// 发送(触发)事件
/// “Command”和“Message”无法得到区分,在此处Dic_Command和Dic_View都要遍历
/// 为什么使用Type?因为只有发送的时候才有实例化控制器的必要。
/// 事件这个东西是实时性的,如果事件没触发,或者永远不会触发,然后实例化会浪费资源
/// 当事件触发的时候再通过反射实例化事件类。之前注册的时候通过typeof()得到对应类的Type类型的对象。
/// 对于程序中用到的每一个类型,CLR都会创建一个包含这个类型信息的Type类型的对象。
/// </summary>
/// <param name="eventName">事件名</param>
/// <param name="data">随事件发送的内容</param>
public static void SendEvent(string eventName, object data = null)
{
// 控制器响应事件
if (Dic_Command.ContainsKey(eventName))
{
Type t = Dic_Command[eventName];
// Activator.CreateInstance创建一个泛型参数所属类型的对象
CBController controller = (CBController)Activator.CreateInstance(t);
// 只使用了命令模式,没有使用观察者模式
// 执行命令对象里面对应的方法
controller.Execute(data);
}

// 视图响应事件
foreach (View view in Dic_View.Values)
{
if (view.EventsObserver.Contains(eventName))
{
view.HandleEvent(eventName, data);
}
}
}
}
}

View

View.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace MVCCore
{
// 视图层,用于显示数据,接收用户输入
public abstract class View : MonoBehaviour
{
// 视图的名称
public abstract string NAME { get; }
// 存储视图所监听的消息
public List<string> EventsObserver = new List<string>();

// 监听关心的消息,添加进EventsObserver列表
// 虚方法,子类可不选择重写,abstract是必须重写
public virtual void RegisterEvents(){}

// 视图对模型的引用
// View是需要引用一个模型的
protected T GetModel<T>() where T : Model
{
return MVC.GetModel<T>();
}

/// <summary>
/// 监听到消息后所要处理的逻辑
/// abstract,子类必须实现自己处理事件的方法
/// </summary>
/// <param name="eventName">命令名称</param>
/// <param name="data">跟随命令的参数</param>
public abstract void HandleEvent(string eventName, object data);

/// <summary>
/// 发送命令
/// </summary>
/// <param name="eventName">命令名称</param>
/// <param name="data">跟随命令的参数</param>
protected void SendEvent(string eventName, object data = null)
{
MVCManager.SendEvent(eventName, data);
}

/// <summary>
/// 注册视图的时候把View需要监听的事件也注册了,有些View没有显示的时候没有必要。
/// 所以在此处由子类调用这个方法才注册。
/// </summary>
protected void RegisterView()
{
MVCManager.RegisterView(this);
}
}
}

Model.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace MVCCore
{
// 模型层,用于存放数据
// 此模型层不仅保管了实体数据,还包括了对这些数据的操作
// 完全可以像PureMVC一样,给Model引入一个代理类ModelProxy负责数据的改动
// Model只做实体数据类
public abstract class Model
{
// 模型名称
// 让子类维护自己的“子类名”
public abstract string NAME { get; }

/// <summary>
/// 模型的变动需要向外发送事件
/// </summary>
/// <param name="eventName">命令名称</param>
/// <param name="data">跟随命令的参数</param>
protected void SendEvent(string eventName, object data = null)
{
MVCManager.SendEvent(eventName, data);
}
}
}

Controller

Controller.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace MVCCore
{

// 控制器层:用于逻辑处理

// 命令抽象类
// 命令模式:Encapsulate a request as an object,
// thereby letting you parameterize clients with different requests, queue or log requests,
// and support undoable operations...
// 需要维护对Model和View的引用,这个引用没有在这里维护,在MVCManger里用字典统一管理了。
public abstract class Controller
{
/// <summary>
/// 监听到消息后处理某些逻辑,抽象执行命令方法
/// </summary>
/// <param name="data">给Model或View的消息内容</param>
public abstract void Execute(object data);

/// <summary>
/// 命令对象对Model接收者的引用
/// </summary>
/// <typeparam name="T">泛型</typeparam>
/// <returns>Model子类对象</returns>
protected T ReferenceModel<T>() where T : CBModel
{
return MVCManager.GetModelObj<T>();
}

/// <summary>
/// 命令对象对View接收者的引用
/// </summary>
/// <typeparam name="T">泛型</typeparam>
/// <returns>View子类对象</returns>
protected T ReferenceView<T>() where T : CBView
{
return MVCManager.GetViewObj<T>();
}

/// <summary>
/// 添加模型
/// </summary>
/// <param name="model">Model子类对象</param>
protected void AddModelObj(Model model)
{
MVCManager.RegisterModel(model);
}

/// <summary>
/// 添加视图
/// </summary>
/// <param name="view">View子类对象</param>
protected void AddViewObj(View view)
{
MVCManager.RegisterView(view);
}

/// <summary>
/// 注册控制器(自身)
/// </summary>
/// <param name="eventName"></param>
/// <param name="commandObj"></param>
protected void AddCommand(string eventName, Type commandObj)
{
MVCManager.RegisterCommand(eventName, commandObj);
}
}
}

GameDefine.cs 系统常量

1
2
3
4
5
6
7
8
9
10
11
12
13
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;

public class GameDefine
{
// 定义命令 命令是对Controller来说的
public const string command_Init = "Command_Init";

// 定义消息 消失是对View说的
public const string message_UpdateCoin = "Message_UpdateCoin";
}

使用

实现点击界面“增加金币”按钮,显示出来的金币数量增加。

初始化框架

Init.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using UnityEngine;
using System.Collections;
using UICore;

public class Init : MonoBehaviour
{
// 这儿要使用Awake,不能使用Start
// Awake在所有Start之前执行。
// 所有启动的地方都要写在Awake中
void Awake()
{
Debug.Log("启动框架");

// 注册控制器(命令与控制器进行绑定)
MVC.RegisterController(GameDefine.command_Init, typeof(InitCommand));

// 发送命令(启动框架)
MVC.SendEvent(GameDefine.command_Init);
}
}

Controller文件夹下

InitCtrl.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using MVCCore;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

public class InitCommand : Controller
{
public override void Execute(object data)
{
// 注册所有的模型
RegisterAllModel();
// 注册所有命令
RegisterAllCommand();
}

/// <summary>
/// 调用MVCManager的RegisterModel()方法,给字典添加Model类的子对象
/// Model在此统一注册,不像View在各自启动的视图注册,是因为View有Awake或Start方法可以调用注册自己的方法
/// 但是Model没有,Model没有可以初始化的方法。
/// </summary>
private void RegisterAllModel()
{
MVCManager.RegisterModel(new InfoModel());
}

/// <summary>
/// 命令名称与命令对象进行绑定,如果没有绑定,SendEvent(命令名字)方法虽然调用命令,但命令对象的Execute()方法是不会被执行的。
/// </summary>
private void RegisterAllCommand()
{
// MVCManager.RegisterCommand(GameDefine.command_BuyGoods, typeof(BuyGoods));
}

// 注册所有的视图(如果一个视图里面有监听消息,那么就一定要注册)
// 不在这儿注册,因为注册视图的时候把视图监听事件也绑定了,看MVCManager的RegisterView方法。如果有些视图还没显示,没必要绑定事件。
// private void RegisterAllView()
// {
//
// }
}

View文件夹下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using MVCCore;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;

public class MainPanelView : View
{
public override string NAME
{
get
{
return "MainPanelView";
}
}

private Button btn_GotoShop;

// Use this for initialization
void Start ()
{
// 在此处注册,此时面板和View类被激活,可以绑定事件。
RegisterView();
btn_GotoShop = transform.Find("Btn_GotoShop").GetComponent<Button>();

btn_GotoShop.onClick.AddListener(ShowShop);
}

public void ShowShop()
{
// 发送消息
// SendEvent("Msg_ShowShop");
}

public override void HandleEvent(string eventName, object data)
{

}
}

Model文件夹下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using MVCCore;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

public class InfoModel : Model
{
// 所有金币的数量
int allCoinCount = 0;

public override string NAME
{
get
{
return "InfoModel";
}
}
}
0%