映射关系在程序中的表示主要有以下三种方式:
一对一 键值对的方式,可以用Dictionary<key,value>
一对多 可以用字典套集合 Dictionary<key,List<object>>
多对多 可以用字典套链表 Dictionary<key,Node>
链表
我们知道链表是节点连接组成的,而节点是由数据域和指针域构成的,顾名思义,数据域data就是存数据的,当然数据类型得我们自己看需求定义,指针域就是存node的引用,一般用next表示。
下面我通过一个多对多的映射关系,演示一下
1. 链表中的节点;
2. 多对多关系的实现;
3. 链表中元素的添加,移除。
那么下面我定义一个类表示节点:
public class EventNode
{
/// <summary>
/// 当前数据
/// </summary>
public MonoBase data;
/// <summary>
/// 下一个节点
/// </summary>
public EventNode next;
/// <summary>
/// 构造函数
/// </summary>
public EventNode(MonoBase data)
{
this.data = data;
this.next = null;
}
}
我定义了很多条消息,每个消息可以有多个脚本监听,每个脚本中可以添加很多条消息。如何实现如下:
我可以先写一个消息类MsgBase
如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MsgBase {
public ushort msgId;
/// <summary>
/// 得到Manager
/// </summary>
/// <returns></returns>
public ManagerID GetManager()
{
int managerId = (int)msgId / FrameTools.MsgPan;
return (ManagerID)( FrameTools.MsgPan * managerId);
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="msgId">msgId消息Id</param>
public MsgBase(ushort msgId)
{
this.msgId = msgId;
}
}
我定义了每个消息类,我每个消息有自己独特的id,所以我们可以定义一个数据结构,来实现多条消息对应多个脚本。
/// <summary>
/// 存储注册消息 键msgId 值 脚本链表
/// </summary>
private Dictionary<ushort, EventNode> eventTree = new Dictionary<ushort, EventNode>();
由于我们采用的是字典键值对的方式,所以我们可以定义四个方法,
1. 注册消息----脚本
2. 注册脚本----消息
3. 移除消息----脚本
4. 移除脚本----消息
具体方法如下:
/// <summary>
/// 往一个脚本里注册若干消息
/// </summary>
/// <param name="mono">要注册的脚本</param>
/// <param name="msgs">一个脚本可以注册多条消息</param>
public void RegisterMsg(MonoBase mono,params ushort[] msgs )
{
for (int i = 0; i < msgs.Length; i++)
{
EventNode node = new EventNode(mono);
RegisterMsg(msgs[i], node);
}
}
链表的增加,每次把需要增加的节点,跟在链表的最后一个节点后面。
/// <summary>
/// 根据msgId注册一个消息链表
/// </summary>
/// <param name="msgId">根据msgId</param>
/// <param name="data">node链表</param>
public void RegisterMsg(ushort msgId, EventNode node)
{
if (!eventTree.ContainsKey(msgId))
{
eventTree.Add(msgId, node);
}
else
{
EventNode temp = eventTree[msgId];
//如果当前键对应的空间不止一个节点
//那么遍历节点,找到最后为空的那个节点,
while (temp.next != null)
{
temp=temp.next;
}
//把node添加在最后
temp.next = node;
}
}
/// <summary>
/// 去掉一个脚本的若干个消息
/// </summary>
/// <param name="mono">mono脚本</param>
/// <param name="msgs">消息id数组</param>
public void UnRegisterMsg(MonoBase mono, params ushort[] msgs)
{
for (int i = 0; i < msgs.Length; i++)
{
UnRegisterMsg(msgs[i], mono);
}
}
链表节点的移除,我们可以先去,注意我们要移除的节点有下列三种情况:
1. 在链表的头结点(在链表的头结点也分两种情况,1.头结点后面没有其他节点2.链表只有一个节点,这个节点就是头结点)
2. 在链表的中间
3. 在链表的尾部
/// <summary>
/// 去掉一个消息链表
/// </summary>
/// <param name="msgId">消息id</param>
/// <param name="node">脚本</param>
public void UnRegisterMsg(ushort msgId, MonoBase node)
{
if (!eventTree.ContainsKey(msgId))
{
Debug.LogWarning("not contail id==" + msgId);
return;
}
else
{
EventNode temp = eventTree[msgId];
//去掉头部,包含两种情况
if (temp.data == node)
{
EventNode header = temp;
//后面多个节点
if (header.next != null)
{
eventTree[msgId] = header.next;
header.next = null;
}
else //只有一个节点的情况
{
eventTree.Remove(msgId);
}
}
else //去掉尾部和中间的节点
{
while (temp.next != null && temp.next.data != node) //只要下一个不是空,或者下一个node存的不是我想要找的,那么我就一直往下找
{
temp = temp.next;
} //表示已经找到该节点
//没有引用 ,自动释放
if (temp.next.next != null) //去掉中间
{
EventNode currentNode = temp.next;
temp.next = currentNode.next;
currentNode.next = null;
}
else //去掉尾部
{
temp.next = null;
}
}
}
}