专栏首页GreenLeavesC# Command命令(行为型模式)+队列 实现事务,带异步命令重试机制和生命周期

C# Command命令(行为型模式)+队列 实现事务,带异步命令重试机制和生命周期

一、简介

耦合是软件不能抵御变变化的根本性原因,不仅实体对象与实体对象之间有耦合关系(如创建性设计模式存在的原因),对象和行为之间也存在耦合关系.

二、实战

1、常规开发中,我们经常会在控制器中或者Main方法中调用多个对象,进行批量的操作(完成一次事务性的操作),像下面这样:

    /// <summary>
    /// 设计模式之Command命令模式
    /// </summary>
    public class Program
    {
        public static void Main(string[] args)
        {
            //模拟持久化内容到文档中
            var doc = new Document();
            var result=doc.WriteText("小超");

            if (result)
            {
                //持久化成功,记录日志
                var log = new Log();
                var logRes = log.WriteLog("小超写入到文档中成功");
                if (logRes)
                {
                    Console.WriteLine("事务性操作成功!");
                }
                else
                {
                    Console.WriteLine("事务性操作失败!");
                }
            }

            Console.ReadKey();
        }
    }

    /// <summary>
    /// 模拟文档对象
    /// </summary>
    public class Document
    {
        public bool WriteText(string content)
        {
            //持久化到对应的数据容器

            return true;
        }
    }

    /// <summary>
    /// 模拟日志对象
    /// </summary>
    public class Log
    {
        public bool WriteLog(string logContent)
        {
            //持久化到对应的数据容器
            return true;
        }
    }

ok,上面的硬编码可以很好的完成需求,但是如果中间发生异常,上的代码将无法支持撤销和回滚.注:这里假设持久化到文档和持久化到日志是一个事务操作(即他们两个必须同时成功,这个操作才算完成,否则就需要回滚).关于事务,和数据库操作一样,使用过SqlTransaction对象的都知道下面这几个方法:

如果我们传入的批量操作Sql(一般只用于增删改,查可以忽略)中有一个发生异常,那么我们就可以调用Dispose方法(释放资源)和Rollback方法,来对事务进行回滚.但是我们上面中的示例明显不支持,所以这个时候我们就需要引入Command命令模式,将两个操作合并为一个操作.在进行最终的提交,失败则回滚,如果涉及非托管资源,不论成功如否都需要释放资源.所以升级代码如下:

    /// <summary>
    /// 设计模式之Command命令模式
    /// </summary>
    public class Program
    {
        public static void Main(string[] args)
        {
            var document = new Document("小超1");
            var command = new DocumentCommand(document);
            var document_3 = new Document("小超3");
            var command_3 = new DocumentCommand(document_3);
            var document_1 = new Document("小超");
            var command_1 = new DocumentCommand(document_1);
            var log = new Log("日志内容");
            var command_2 = new LogCommand(log);
            var manager = new CommandManager();
            manager.Commands.Enqueue(command_3);
            manager.Commands.Enqueue(command);
            manager.Commands.Enqueue(command_1);
            manager.Commands.Enqueue(command_2);
            
            manager.Execute();
            Console.ReadKey();
        }
    }

    /// <summary>
    /// 模拟文档对象
    /// </summary>
    public class Document
    {
        private Document() { }

        public string Content { get; }

        public Document(string content)
        {
            Content = content;
        }

        public bool WriteText(string content)
        {
            //持久化到对应的数据容器
            if (content == "小超")
                throw new Exception("写入文档异常");
            else
                return true;
        }
    }

    /// <summary>
    /// 模拟日志对象
    /// </summary>
    public class Log
    {
        private Log() { }

        public string Content { get; set; }

        public Log(string logContent)
        {
            Content = logContent;
        }

        public bool WriteLog()
        {
            //持久化到对应的数据容器
            return true;
        }
    }

    /// <summary>
    /// 命令约束
    /// </summary>
    public interface ICommand
    {
        void Execute();

        void Undo();

        void Redo();
    }

    /// <summary>
    /// 命令基类
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class Command<T>
    {
        /// <summary>
        /// 命令Id,方便回回滚数据
        /// </summary>
        protected Guid CommandId { get; set; } = Guid.NewGuid();
    }

    /// <summary>
    /// 文档操作命令对象
    /// </summary>
    public class DocumentCommand : Command<Guid>,ICommand
    {
        /// <summary>
        /// 模拟文档内容数据容器
        /// </summary>
        public Dictionary<Guid, Document> DocumentContents { get; set; } = new Dictionary<Guid, Document>();

        private DocumentCommand() {}

        private Document _document;

        public DocumentCommand(Document document)
        {
            _document = document;
        }

        public void Execute()
        {
            //模拟持久化到数据容器中
            try
            {
                Console.WriteLine("当前命令Id:{0},参数内容:{1}", CommandId, JsonConvert.SerializeObject(_document));
                _document.WriteText(_document.Content);
                DocumentContents.Add(CommandId, _document);
                Console.WriteLine("当前命令执行成功,命令Id:{0},参数内容:{1}", CommandId, JsonConvert.SerializeObject(_document));
            }
            catch (Exception ex)
            {
                Console.WriteLine("当前命令执行失败,命令Id:{0},参数内容:{1},异常信息:{2}", CommandId, JsonConvert.SerializeObject(_document),ex.Message);
                throw ex;
            }
            
        }


        public void Redo()
        {
            //重新执行Execute方法
            Execute();
        }

        /// <summary>
        /// 事物操作,如果后面的操作发生异常,这里也需要回滚
        /// </summary>
        public void Undo()
        {
            var value = default(Document);
            if (DocumentContents.ContainsKey(CommandId))
            {
                value = DocumentContents[CommandId];
            }
            else {
                Console.WriteLine("文档命令执行发生异常,当前命令Id:{0},当前文档信息:{1}", CommandId, JsonConvert.SerializeObject(_document));//记录日志
            }
            if (!DocumentContents.Remove(CommandId))
                Console.WriteLine("文档命令执行发生异常,当前命令Id:{0},当前文档信息:{1}", CommandId, JsonConvert.SerializeObject(_document));//记录日志
            else
                Console.WriteLine("事物回滚,将插入到文档中的内容删除,被删除的对象是:{0}", JsonConvert.SerializeObject(_document));//记录日志
        }
    }

    /// <summary>
    /// 日志操作命令
    /// </summary>
    public class LogCommand: Command<Guid>, ICommand
    {
        /// <summary>
        /// 模拟文档内容数据容器
        /// </summary>
        public Dictionary<Guid, string> LogContents { get; set; } = new Dictionary<Guid, string>();

        private LogCommand() { }

        private Log _log;

        public LogCommand(Log log)
        {
            _log = log;
        }

        public void Execute()
        {
            //模拟持久化到数据容器中
            try
            {
                _log.WriteLog();
                LogContents.Add(CommandId, _log.Content);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }


        public void Redo()
        {
            //重新执行Execute方法
            Execute();
        }

        /// <summary>
        /// 事物操作,如果后面的操作发生异常,这里也需要回滚
        /// </summary>
        public void Undo()
        {
            var value = "";
            if (LogContents.ContainsKey(CommandId))
            {
                value = LogContents[CommandId];
            }
            else
            {
                Console.WriteLine("日志命令执行发生异常,当前命令Id:{0},当前日志信息:{1}", CommandId, JsonConvert.SerializeObject(_log));//记录日志
            }
            if (!LogContents.Remove(CommandId))
                Console.WriteLine("日志命令执行发生异常,当前命令Id:{0},当前日志信息:{1}", CommandId, JsonConvert.SerializeObject(_log));//记录日志
            else
                Console.WriteLine("事物回滚,将插入到日志中的内容删除,被删除的内容是:{0}", value);//记录日志
        }
    }

    /// <summary>
    /// 命令管理器
    /// </summary>
    public class CommandManager
    {
        public Queue<ICommand> Commands = new Queue<ICommand>();

        public Queue<ICommand> UndoCommands = new Queue<ICommand>();

        public Queue<ICommand> SuccessCommands = new Queue<ICommand>();

        /// <summary>
        /// 命令执行
        /// </summary>
        public void Execute()
        {
            foreach (var command in Commands)
            {
                try
                {
                    Console.WriteLine("命令开始执行,当前命令名称:{0}", command.GetType().Name);//记录日志
                    command.Execute();
                    Console.WriteLine("命令执行结束,当前命令名称:{0}", command.GetType().Name);//记录日志
                    Console.WriteLine();
                    SuccessCommands.Enqueue(command);
                }
                catch
                {
                    Console.WriteLine("命令执行结束,当前命令名称:{0}", command.GetType().Name);//记录日志
                    Undo(command);
                    Redo();
                    RollBack();
                    break;
                }
                
            }
        }

        public void Undo(ICommand command)
        {
            if (CanUndo)
            {
                UndoCommands.Enqueue(command);
            }
            else {
                Console.WriteLine("当前命令队列没有排队的命令!");//记录日志
            }
        }

        /// <summary>
        /// 命令重试
        /// </summary>
        public void Redo()
        {
           
            //这个最大重试次数,建议读取配置文件
            var tryCount = 3;
            var time = 0;
            if (CanRedo)
            {
                    var command = UndoCommands.Dequeue();
                   //开启一个新线程进行重试操作,重试3次,失败则发送邮件通知,或者记录日志

                    Task.Run(() =>
                    {
                        var index = 1;

                        while (true)
                        {
                            Interlocked.Add(ref time, index);
                            try
                            {
                                command.Redo();
                            }
                            catch (Exception ex)
                            {
                                if (time == tryCount)
                                {
                                    Console.WriteLine("当前命令:{0},重试{1}次后执行失败,请检查原因!异常信息如下:{2}", typeof(DocumentCommand).Name, tryCount, ex.Message);
                                    break;
                                }
                            }
                        }
                    });
                }
            
        }

        /// <summary>
        /// 事务回滚
        /// </summary>
        public void RollBack()
        {
            Console.WriteLine();
            if (SuccessCommands.Count > 0)
            {
                Console.WriteLine("事物发生异常,记录开始回滚!");
                foreach (var command in SuccessCommands)
                {
                    command.Undo();
                }
                Console.WriteLine("事物回滚结束");
            }
            else {
                Console.WriteLine("当前没有需要回滚的操作!");
            }
            Console.WriteLine();
        }

        private bool CanUndo { get { return Commands.Count > 0; } }

        private bool CanRedo { get { return UndoCommands.Count > 0; } }
    }

注:上面所有的Console.WriteLine都需要改成异步日志功能.异步重试中的Concosole.WriteLine因为Ms做了同步处理,所以输出可能会异常.所以异步写日志比较合理.

这里在提一点,如果需要实现多个命令组成一个复合命令,可以使用Composite组合模式将多个命令组成一个复合命令,来实现.后续的随笔中我会介绍.

文章中的代码有bug,或者不当之处,请在下面指正,感谢!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • FactoryMethod工厂方法模式(创建型模式)

    整个抽象的游戏设施建造系统相对变化较慢,本例中只有一个Build的创建方法,而Build内部的方法实现,该实现依赖与各种具体的实现,而这些实现变化的非常频繁,现...

    郑小超.
  • .Net 从零开始构建一个框架之基本实体结构与基本仓储构建

    本系列文章将介绍如何在.Net框架下,从零开始搭建一个完成CRUD的Framework,该Framework将具备以下功能,基本实体结构(基于DDD)、基本仓储...

    郑小超.
  • 领域驱动系列二策略模式的应用

    随着模型的不断扩大,发现模型中不单单只有"名词",还有许多"谓词",简言之,就是领域知识中,会参杂者许多的业务规则,他们和实体一样,都扮演者领域模型中的核心角色...

    郑小超.
  • Magicodes.WeiChat——使用OAuth 2.0获取微信用户信息

    使用Magicodes.WeiChat,可以很方便的获取到微信用户的信息。在使用OAuth 2.0之前,你先需要做以下操作:

    雪雁-心莱科技
  • .net mvc中一种简单的工作流的设计

    开篇前的废话:工作流是我们在做互联网应用开发时经常需要用到的一种技术,复杂的工作流我们基本是借助一些开源的 工作流项目来做,比如 ccflow等,但是有时候,我...

    CherishTheYouth
  • JDK源码分析 反射

    对于JDK源码分析的文章,仅仅记录我认为重要的地方。源码的细节实在太多,不可能面面俱到地写清每个逻辑。所以我的JDK源码分析,着重在JDK的体系架构层面,具体源...

    Yano_nankai
  • APK安装流程详解1——有关"安装ing"的实体类概述

    该类包含了从AndroidManifest.xml文件中收集的所有信息。 PackageInfo.java源码地址 通过源码我们知道PackageInfo是...

    隔壁老李头
  • 使用iText5来处理PDF

    项目要求,通过pdf模板,把用户提交的数据保存到一个PDF文件中。其中有文字内容,也有图片。之前选了aspose.pdf,因为抠门,不能花钱买,就从网上找的的开...

    徐大嘴
  • 委托与事件-委托事件案例(三)

      这两天一直在想如何结合实际案例来结束委托与事件的讲解,下面讲解两个事例,用来加深对委托及事件的理解。

    小世界的野孩子
  • 用SignalR 2.0开发客服系统[系列2:实现聊天室]

    前言 交流群:195866844 上周发表了 用SignalR 2.0开发客服系统[系列1:实现群发通讯] 这篇文章,得到了很多帮助和鼓励,小弟在此真心的感谢大...

    GuZhenYin

扫码关注云+社区

领取腾讯云代金券