1. 什么是耦合?有什么坏处?
藕这种东西大家都听说过吧?
耦合就是像耦一样的东西…藕断…嘶…连
短路3s…
我们修改A模块,结果导致B崩溃了,修改B模块导致C模块崩溃了,至此996熬夜加班,万古如长夜
如果A,B,C之间半点关系没有,那是不是可以哪里坏了点哪里!!哪里坏了修哪里,即使我不断反向修复,那么坏的也最多一个模块,而且,要是我们把不同的功能分开放,也方便改不是?
我们在编写代码时应该考虑到以后可能的修改需求,有良好的拓展性,但是我们不期待完全解耦,这是不现实的,过于严重的解耦会导致代码割裂而散乱,我们追求”低耦合,高内聚“。
耦合性:也称块间联系。指软件系统结构中各模度块间相互联系紧密程度的一种度量。模块之问间联系越紧密,其耦合性就越答强,模块的独立性则越差。
来自 https://blog.csdn.net/qq_39736982/article/details/81478176
2. 如何做到解耦合?
打个(不)恰当的比喻就像是一对热恋的情侣…十分耦合…咳咳…我这是在讨论技术…(我朋友爱看,呸,看什么,这是技术文)…
如何解耦合…就是如何逐步降低他们的关系(狗头护体,滑稽护体)
显然是让他们不再相互依赖!
两个模块之间降低依赖程度,就是耦合度低了
但是依赖是不可避免的,就像人与人总是有联系。
没有人是与世隔绝的孤岛,每个人都是广袤大陆的一部分
我们直接没有太多直接关联,笔者不会向你收钱,你的钱也不是我的(哭)
也就是说,笔者发几篇文章也是发在CSDN而不是直接发给读者,读者想看也只能通过CSDN而不是找笔者要。
我们的直接联系就变少了,通过一个类似的中介者也可以让代码耦合变少(中介模式)
3. 简易消息机制解耦合
来到了本文核心内容,消息机制
那么请思考,如何创建这个中介?
还是上文那对小情侣…他们通过微信联系,那么微信发的就是消息
男: 亲爱的,星期日 我们 去 森林公园 玩吧
女: 好啊!不见不散
那么继续思考,谁是中介? 当然是微信了,我们要创造一个类似微信,CSDN的中介,也就是专门打造一个平台,负责传递消息
女生只要想去公园玩,女生就会通过微信去联系男生,而非直接去找男生。
穿过多啦A梦的任意门回到文章内…
首先,我们要清楚实现什么
1. 微信(中介平台)
2. 微信消息(包含时间,地点,动作,对象)
只要中介者不崩溃,A出问题和B没关系,B坏了和A也没关系,这不就降低了耦合度
给消息命名
先从微信开始
消息辣莫多,我怎么知道哪条是哪条?
怎么区分是哪条消息?我们应该给每条消息加一个标识符。
可以用字符串或者枚举甚至int等多种类型作为标识符
各有优点,字符串是方便,但是字符串性能不是很理想,因为比对一个10长度的字符串就是比对10个char,远低于比对一个int。
int会没有描述性,123456789,请找到跳跃对应的事件…明显找不到嘛
枚举呢,每次都需要手动配置,但是兼顾的性能和描述性,也不是很方便。
虽然很不愿意,但是的确很难找到更合适的消息标识了。
我使用了枚举标记一类事件,需要增加新的事件就添加枚举
public enum MessageList
{
Meet //约会的标志,这个消息叫Meet
}
(2)约定消息格式
我们怎么约定消息的格式?意思就是模块间消息只能这么写,毕竟是写给电脑看的,电脑不像人一样有着智能,它只能读懂固定格式的语言
不同模块直接传递的消息,有时候要有参数,一般也就4个以内,好在C#为开发者提供了泛型,可以自行决定参数类型
namespace xxx
{
public delegate void Message();
public delegate void Message<T>(T prarm1);
public delegate void Message<T, P>(T prarm1, P param2);
public delegate void Message<T, P, W>(T prarm1, P param2, W param3);
public delegate void Message<T, P, W, G>(T prarm1, P param2, W param3, G param4);
}
读者可能会在网上看到很多使用object[]和parm的消息机制,最大的问题就是在值类型转object时产生装箱,所以这里采用泛型规避这个问题,缺点就是代码变得难以维护,因为不只是委托需要使用大量泛型,注册和取消注册,执行都需要。
(3)模拟消息中心(中介者)
那我们把消息存在哪里,当然是在中介里构造一个数据库
引入一个概念,叫 容器
什么是容器,就像微信的数据库,容纳所有用户的消息.
键就是消息的名字,这样消息就有唯一性了
中介者微信怎么实现,我们用一个类模拟,在其中创建静态Dictionary来存储消息。
public static class MessageCenter
{
//这个类是消息中心,就是我们的微信
.
private static Dictionary<MessageList, Delegate> MessagePool = new Dictionary<MessageList, Delegate>();//这就是我们的数据库,存消息
}
添加一个用来添加消息的方法
//添加到消息中心存储
public static void Add(MessageList messageType, Message message)//传入一个枚举标记这个事件
{
//这里采用switch只是为了让读者更明确分支结构,属于故意而为。
//实际上双分支并不需要采用switch
switch (MessagePool.ContainsKey(messageType)) //判断容器里有没有这个类型的事件
{
case true: //如果有
{
MessagePool[messageType] = (Message)MessagePool[messageType] + message; //委托中加入一个新的方法
break;
}
case false: //如果无
{
MessagePool.Add(messageType, message); //添加一个新的委托
break;
}
}
}
声明一个方法,用来判断是否存在这个事件,并且是不是空
private static bool IsInDic(MessageList messageType) //传入枚举标志
{
//这里两个if可以写在一起,分开写让读者看的更清楚
if (MessagePool.ContainsKey(messageType)) //如果有这个事件
{
if (MessagePool[messageType] != null) //如果这个事件不是空的
{
return true; //返回真
}
}
return false;
}
有消息,自然需要执行
public static void Invoke(MessageList eventType) //传入消息类型
{
if (IsInDic(messageType))//非空
{
Message msg = MessagePool[messageType] as Message; //拿出来这个事件
msg?.Invoke(); //不空就执行他...
}
}
自然也需要移除
public static void Remove(MessageList messageType, Message message)
{
if (MessagePool.ContainsKey(messageType))
{
MessagePool[messageType] = (Message)MessagePool[messageType] - message;
if (MessagePool[messageType] == null)
{
MessagePool.Remove(messageType);
}
}
}
以上几部分在一起就是消息中心的部分代码
消息机制三要素
1.注册Add
2.移除注册Remove
3.发送消息Invoke
(4) 使用
整个过程写的很清楚了,这就是约定一个事件
如何约定一个像上文提到的事件呢
//假设这个函数是入口函数
void Achievement()
{
//两个约定
MessageCenter.Add(MessageList.Meet,和男1去公园玩);
MessageCenter.Add(MessageList.Meet,和男2去公园玩);
//取消一个
MessageCenter.Remove(MessageList.Meet,和男2公园玩);
//执行约会
MessageCenter.Invoke(MessageList.Meet);
}
void 和男1去公园玩()
{
Debug.Log("女主要和男1出去玩了");
}
void 和男2去公园玩()
{
Debug.Log("女主要和男2出去玩了");
}
到这里读者可能会觉得,这仅仅只是在一个方法里执行另外两个方法,有什么用?
笔者想说,如果Achievement方法和这两个方法分别处于两个不同的复杂结构中,我们难以直接直接调用这两个方法,而强行调用会让结构非常混乱,所以我们才建立这样的消息机制来应对,更多的细节更需要读者在实践中慢慢体会。
下一篇文章是笔者在大一时写的填坑笔记对这篇文章进行补充
以上举例只是玩笑,愿天下有情人终成眷属。
感谢看完,小漫画加深感悟,通过中介者模式实现模块之间解耦,但是每个模块都与消息中心耦合。