zoukankan      html  css  js  c++  java
  • 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,或者不当之处,请在下面指正,感谢!

  • 相关阅读:
    深入浅出理解依赖注入这种由外部负责其依赖需求的行为,我们可以称其为 “控制反转(IoC)”
    php 远程下载图片到本地
    深入理解 RESTful Api 架构
    uva 10369 Arctic Network (最小生成树加丁点变形)
    UVALive
    UVA
    UVA
    POJ 1182 食物链(经典带权并查集 向量思维模式 很重要)
    HDU 1829 A Bug's Life (种类并查集)
    UVA
  • 原文地址:https://www.cnblogs.com/GreenLeaves/p/10203351.html
Copyright © 2011-2022 走看看