首页 > 其他分享 >架构演化思考总结(2)

架构演化思考总结(2)

时间:2024-07-31 21:55:24浏览次数:15  
标签:Execute 架构 演化 void class 命令 思考 new public

架构演化思考总结(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对命令模式的介绍:

img

上面小案例正对应对“操作逻辑”进行封装,提炼成命令,那么这样对操作逻辑进行封装有什么好处呢?

显而易见的好处之一就是方便管理,逻辑清晰。进行复杂逻辑开发时候,我们正式把它尽可能提炼封装成方法,为的就是方便管理,而命令模式是对逻辑代码再高一个层次的封装,也就是说从方法抽象成类,显然更加便于管理和复用。

使用命令模式降低复杂逻辑的开发调试难度,你排查一个几百行的大函数的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模式!

谢谢各位能和我一起来探索项目架构设计演化!

标签:Execute,架构,演化,void,class,命令,思考,new,public
From: https://www.cnblogs.com/TonyCode/p/18335588

相关文章

  • 【系统架构设计师】二十一、面向服务架构设计理论与实践②
    目录四、SOA主要协议和规范五、SOA设计的标准要求5.1SOA设计标准5.2服务质量六、 SOA的作用与设计原则七、SOA的设计模式7.1服务注册表模式7.2企业服务总线模式7.3微服务模式八、SOA的构建与实施8.1构建SOA时应该注意的问题8.2SOA的实施过程8.3 业务......
  • 【杂谈】JPA乐观锁改悲观锁遇到的一些问题与思考
    背景接过一个外包的项目,该项目使用JPA作为ORM。项目中有多个entity带有@version字段当并发高的时候经常报乐观锁错误OptimisticLocingFailureException原理知识JPA的@version是通过在SQL语句上做手脚来实现乐观锁的UPDATEtable_nameSETupdated_column=new_value,vers......
  • LLM-文心一言:以太坊2.0架构
    以太坊2.0的架构是一个复杂且不断发展的系统,旨在解决以太坊1.0存在的性能瓶颈和扩展性问题。以下是以太坊2.0架构的主要组成部分和阶段:信标链(BeaconChain):信标链是以太坊2.0的核心组成部分,它负责协调整个网络并管理权益证明(PoS)协议。信标链本身不存储任何状态信息、DApps或其他......
  • 架构与思维:DNS在架构中的使用
    1介绍DNS(DomainNameSystem,域名系统)是一种服务,它是域名和IP地址相互映射的一个分布式数据库,能够使人更方便的访问互联网,而不用去记住能够被机器直接读取的IP地址数串。简单来说,DNS就是一个将我们输入的网址(比如www.baidu.com)转换成对应的IP地址(比如192.0.2.1)的系统。这个过程......
  • springboot+vue基于微服务架构的设备管理系统【程序+论文+开题】-计算机毕业设计
    系统程序文件列表开题报告内容研究背景随着企业规模的不断扩大与信息化程度的日益加深,设备管理成为企业运营中不可或缺的一环。传统集中式架构的设备管理系统在面对大规模数据处理、高并发访问及系统扩展性等方面显得力不从心。微服务架构以其高度的模块化、灵活的服务部署......
  • 达梦数据库dm8版本Mpp集群原理系统架构和搭建测试
    达梦数据库dm8版本Mpp集群原理系统架构和搭建测试    当前主流的数据库系统架构有完全共享、共享存储、完全不共享和完全对等不共享几种。其中完全共享体系如SMP服务器,局限于单节点服务器,通常价格比较昂贵,其扩展性和性能受到相应的限制。共享存储体系允许系统......
  • Server端架构
    Server:创建TCP的socket,监听客户端的连接。当一个客户端连接server的时候,server会单独创建一个client用来维护跟某一个客户端的连接,进行一个收发消息。client会有多个,一个客户端对应一个client,所以说后续不同的请求是通过client来调用controller进行处理。server会对client做一......
  • 架构 | 互联架构的图建模分析
    互联网络泛泛谈互联网络是一种图结构,节点之间通过边连接。比如一般总线计算机架构中CPU、Memory、Timer等模块看作节点,通过总线相连。每个节点在网络中的身份是不对等的,有master和slave之分,或者按TLM中的模型initiator和target。Initiator有权申请发起传输事务,而......
  • 用依赖倒置和控制反转,突破Golang循环调用限制之后的思考
    在软件开发中,随着项目规模的扩大和业务逻辑的复杂化,重构代码变得越来越重要。本文将介绍如何在既有代码基础上,通过依赖倒置(DIP)和控制反转(IoC),实现新增加的代码可以循环引用到服务层的代码。然后,我们将探讨接口隔离、设计小而清晰的接口和包,以及共同依赖原则等内容。包引用时的......
  • 即时聊天系统(类微信社区)开发需求与功能架构分析
    在当今数字化时代,即时通讯应用如微信、QQ、Telegram等已成为人们日常生活中不可或缺的一部分。这些平台不仅支持基础的文字、图片、视频消息传输,还集成了朋友圈分享、个人信息管理、以及高效的通知系统等多元化功能。以下是对一个类微信即时聊天系统开发需求与功能架构的详细分析......