架构演化考虑总结(2)
架构演化考虑总结(2)
—-–从指令形式中来探究处理依靠联系
在正式引进指令形式的概念之前,咱们先从简略的事例来逐渐演化咱们在书面上常见到的内容。
public interface ICommand
{
void Execute();
}
public class PlayMusicCommand : ICommand
{
public void Execute()
{
Debug.Log("你说家是仅有的城堡,跟着稻香一路奔驰~");
}
}
var Start()
{
var command = new PlayMusicCommand();
command.Execute();
}
这儿咱们界说一个指令接口,假如是指令,必定要完结的一个履行办法。
PlayMusicCommand 完结接口,此指令的效果便是播映Jay的《稻香》,假如想完结播映音乐功用,直接履行对应指令的办法即可!
体现出指令的实质,咱们把要所作的内容或许操控逻辑封装到一同,当咱们需求履行它时分,下达履行指令的办法即可!
来看AI对指令形式的介绍:
上面小事例正对应对“操作逻辑”进行封装,提炼成指令,那么这样对操作逻辑进行封装有什么优点呢?
清楚明了的优点之一便是便利办理,逻辑明晰。进行杂乱逻辑开发时分,咱们正式把它尽可能提炼封装成办法,为的便是便利办理,而指令形式是对逻辑代码再高一个层次的封装,也便是说从办法笼统成类,明显愈加便于办理和复用。
运用指令形式下降杂乱逻辑的开发调试难度,你排查一个几百行的大函数的bug必定比封装拆分红几个函数或许是几个对应的指令的情况要费事。比方咱们需求进行某个杂乱操作,可是咱们对它进行拆分封装,分红几个Command来履行,这样既能够分发给几个搭档一同协作杂乱逻辑开发,没有与中心逻辑操控脚本发生过多的耦合。
当然另一方面分管了操控脚本Controller的操控压力,使其没那么臃肿。
public interface ICommand
{
void Execute();
}
public class ACommand
{
public void Execute()
{
Debug.Log("Execute A Command");
}
}
public class BCommand
{
public void Execute()
{
Debug.Log("Execute B Command");
}
}
public class CCommand
{
public void Execute()
{
Debug.Log("Execute C Command");
}
}
void Start()
{
var commands = new List<ICommand>();
commands.Add(new ACommand());
commands.Add(new BCommand());
commands.Add(new CCommand());
commands.ForEach(c=>c.Execute());
}
指令形式–带着参数
假如咱们要履行需求参数才干进行的指令呢?
好,接下来咱们完结能够带着参数的指令,十分简略,只需求给履行的指令中声明参数即可!
interface ICommand
{
void Execute();
}
public class BuyGoodsCommand : ICommand
{
private int goodsId;
private int goodsCount;
public BuyGoodsCommand(int id,int count)
{
goodsId = id;
goodsCount = count;
}
public void Execute()
{
Debug.Log($"购买了id为{goodsId}的产品{goodsCount}个");
//履行相关的购买逻辑
//......
}
}
public class Test : MonoBehaviour
{
private void Start()
{
var buyGoodsCommand = new BuyGoodsCommand(1, 15);
buyGoodsCommand.Execute();
}
}
指令形式–吊销功用
接下来接着向指令形式的功用完结跨进,在刚触摸指令形式的时分,会猎奇的想到,已然把指令都封装好一步步履行了,那能不能吊销现已履行好的行为呢?笔者也是在学习到指令形式之后才联想到各种修改东西的Ctrl+Z的效果的完结思路。那咱们往指令形式中增加一个吊销功用。
当然要履行吊销指令,要有个容器来存储现已履行的指令,这儿运用的是List,也能够用Stack
和Queue,当然运用栈就能够完结Ctrl + Z的逐渐吊销功用了!
interface ICommand
{
void Execute();
void Undo();
}
public class BuyGoodsCommand : ICommand
{
private int goodsId;
private int goodsCount;
public BuyGoodsCommand(int id,int count)
{
goodsId = id;
goodsCount = count;
}
public void Execute()
{
Debug.Log($"购买了id为{goodsId}的产品{goodsCount}个");
//履行相关的购买逻辑
//......
}
public void Undo()
{
Debug.Log($"方才购买的id为{goodsId}的产品{goodsCount}个,现已悉数退货!");
//履行相关的退货操作
//如库存++
//玩家金币++
}
}
public class Test : MonoBehaviour
{
private void Start()
{
var commands = new List<BuyGoodsCommand>();
commands.Add(new BuyGoodsCommand(1, 15));
commands.Add(new BuyGoodsCommand(5, 2));
//履行购买
commands.ForEach(command => command.Execute());
//5号物品不想要了 退货
commands[1].Undo();
}
}
指令形式–指令和履行别离
这儿和上一篇所陈说的依靠联系大致相同,咱们把指令从一个目标降级成办法来看。
咱们常常进行的办法调用这种行为,便是指令和履行未别离的一个比如。即办法调用必定办法中的逻辑履行。
void DoSomethingCommand()
{
Debug.Log("指令履行了!");
}
void Start()
{
DoSomethingCommand();
}
那么指令和履行分隔是怎么样的呢?
咱们能够运用托付来完结,时刻和空间上的别离。
public class A : MonoBehaviour
{
B b;
void Start()
{
b = transform.Find("Animation").GetComponent<B>();
// 注册完结的事情
b.OnDoSomethingDone += DoSomethingCommand;
}
void DoSomethingCommand()
{
Debug.Log("指令履行了!");
}
}
public class B : MonoBehaviour
{
// 界说托付
public Action OnDoSomethingDone = ()=>{};
//当动画播映结束后调用
public void DoSomething()
{
//触发托付中的函数履行
OnDoSomethingDone();
}
}
这样即将履行的指令DoSomethingCommand,会在特定机遇(时刻上别离)由别的一个脚本(空间上别离)调用履行,完结时空别离。
好,咱们现已在办法层面表述出指令的别离,现在咱们回到类这个层面,将Command的声明和履行进行别离。
这就需求一个对托付进行另一层的封装运用,这儿是用托付(能够简略理解为函数容器),存储的是函数(command简化为办法层面),能够运用。对应的将指令晋级晋级成目标,为此也要对
托付进行“晋级”,这儿参阅QFramWork的自界说的事情机制。
自界说事情机制
咱们期望它事情机制具有功用:发送事情功用和主动刊出功用。
发送事情是有必要的,而主动刊出功用要的是当注册事情监听的GameObject的目标Destroy之后,要刊出对事情的监听功用。
现在依照这样的要求来完结接口:
public interface ITypeEventSystem
{
/// <summary>
/// 发送事情
/// </summary>
/// <typeparam name="T"></typeparam>
void Send<T>() where T : new ();
void Send<T>(T e);
IUnRegister Register<T>(Action<T> onEvent);
/// <summary>
/// 刊出事情
/// </summary>
/// <param name="onEvent"></param>
/// <typeparam name="T"></typeparam>
void UnRegister<T>(Action<T> onEvent);
}
//刊出机制
public interface IUnRegister
{
void UnRegister();
}
来侧重完结主动刊出机制:
咱们来声明一个类,来详细履行刊出事情的功用:
public class TypeEventSystemUnRegister<T> : IUnRegister
{
//持有事情机制引证
public ITypeEventSystem TypeEventSystem { get; set; }
//持有待刊出的托付
public Action<T> OnEvent {get;set;}
//详细的刊出机办法
public void UnRegister()
{
//详细便是调用事情机制(体系)对应的办法,刊出掉指定的函数 (OnEvent)
TypeEventSystem.UnRegister(OnEvent);
TypeEventSystem = null;
OnEvent = null;
}
}
当然刊出机遇是在当GameObjet毁掉时分,为此需求一个“触发器”,其挂载在注册事情的GameObject上,当检测到Destroy时分进行触发。
来完结对应的触发器:
/// <summary>
/// 刊出事情的触发器
/// </summary>
public class UnRegisterOnDestroyTrigger : MonoBehaviour
{
private HashSet<IUnRegister> mUnRegisters = new HashSet<IUnRegister>();
public void AddUnRegister(IUnRegister unRegister)
{
mUnRegisters.Add(unRegister);
}
private void OnDestroy()
{
foreach (var unRegister in mUnRegisters)
{
unRegister.UnRegister();
}
mUnRegisters.Clear();
}
}
来对刊出机制的接口拓宽功用,便利在注册事情时分调用一个办法,经过这个办法调用直接将上段代码所示的刊出机制的触发器挂载在GameObject上。
public static class UnRegisterExtension
{
public static void UnRegisterWhenGameObjectDestroyed(this IUnRegister unRegister, GameObject gameObject)
{
var trigger = gameObject.GetComponent<UnRegisterOnDestroyTrigger>();
if (!trigger)
{
trigger = gameObject.AddComponent<UnRegisterOnDestroyTrigger>();
}
trigger.AddUnRegister(unRegister);
}
}
至此,当咱们在运用时分调用一下’UnRegisterWhenGameObjectDestroyed‘办法,将会挂载Tirgger,当物体毁掉时分会触发,完结主动刊出事情,有用的确保了在运用Unity中托付的注册和刊出成对呈现的特征,避免托付中呈现空指针。
好,完结完结主动刊出事情机制,持续完结事情的注册和调用机制。
public class TypeEventSystem : ITypeEventSystem
{
//运用依靠倒转准则
interface IRegistrations
{
}
class Registrations<T> : IRegistrations
{
public Action<T> OnEvent = obj => { };
}
//依据事情的类型来存储 对应的事情Action<T> 被封装成类 以接口类型存储
private Dictionary<Type, IRegistrations> mEventRegistrations = new Dictionary<Type, IRegistrations>();
public void Send<T>() where T : new()
{
var e = new T();
Send<T>(e);
}
//详细发送机制 调用机制
public void Send<T>(T e)
{
var type = typeof(T);
IRegistrations eventRegistrations;
if (mEventRegistrations.TryGetValue(type, out eventRegistrations))
{
//详细调用 “解压 降维” 调用托付
(eventRegistrations as Registrations<T>)?.OnEvent.Invoke(e);
}
}
//注册完结
public IUnRegister Register<T>(Action<T> onEvent)
{
var type = typeof(T);
//详细存储 “加压 升维” 向托付中增加函数
IRegistrations eventRegistrations;
//判别存储的事情类型存在否
if (mEventRegistrations.TryGetValue(type, out eventRegistrations))
{
}
else
{
//不存在就增加一个
eventRegistrations = new Registrations<T>();
mEventRegistrations.Add(type,eventRegistrations);
}
//假如存在就 解压 增加到“解压”好的事情机制中
(eventRegistrations as Registrations<T>).OnEvent += onEvent;
//回来刊出目标需求的数据(引证)实例
// 能够不经过结构函数来对共有拜访目标初始化赋值
return new TypeEventSystemUnRegister<T>()
{
OnEvent = onEvent,
TypeEventSystem = this
};
}
//刊出办法的详细完结
public void UnRegister<T>(Action<T> onEvent)
{
var type = typeof(T);
IRegistrations eventRegistrations;
if (mEventRegistrations.TryGetValue(type,out eventRegistrations))
{
(eventRegistrations as Registrations<T>).OnEvent -= onEvent;
}
}
}
至此自界说的事情机制完结结束!
假如感兴趣,重视其对应的测验事例展现,(将在独自一篇博客介绍此事情机制)
持续推动,运用此机制来完结Command的时空别离:
public interface ICommand
{
void Execute();
}
public class SayHelloCommand
{
public void Execute()
{
// 履行
Debug.Log("Say Hello");
}
}
void Start()
{
// 指令
var command = new SayHelloCommand();
command.Execute();
mTypeEventSystem = new TypeEventSystem();
mTypeEventSystem.Register<ICommand>(Execute).UnRegisterWhenGameObjectDestroyed(gameObject);
// 指令 运用Command目标注册
mTypeEventSystem.Send<Icommand>(new SayHelloCommand());
}
那么比照三种完结办法发现什么?
- 办法:调用即履行!没有别离
- 事情机制:履行在事情注册中完结 有别离
- Command:履行在Command内部完结 有别离
明显,Command对指令和履行的别离程度介于办法和事情机制之间。
要点比照事情机制,在完结自界说办法之前,笔者现已点到托付存储的办法,和在运用封装后托付(自界说事情)能够存储类(指令实例),尽管都是经过托付来存储履行办法,运用Command更为自在一些,能够在自界说的方位和机遇履行,而事情机制一般至少需求经过两个目标才干完好运用。
先写到这儿吧!
下面咱们持续探究指令形式在架构演化中的效果,持续挨近咱们学习中触摸到的Command形式!
谢谢各位能和我一同来探究项目架构规划演化!