一、简介

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

二、实战

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 = ;
var time = ;
if (CanRedo)
{
var command = UndoCommands.Dequeue();
//开启一个新线程进行重试操作,重试3次,失败则发送邮件通知,或者记录日志 Task.Run(() =>
{
var index = ; 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 > )
{
Console.WriteLine("事物发生异常,记录开始回滚!");
foreach (var command in SuccessCommands)
{
command.Undo();
}
Console.WriteLine("事物回滚结束");
}
else {
Console.WriteLine("当前没有需要回滚的操作!");
}
Console.WriteLine();
} private bool CanUndo { get { return Commands.Count > ; } } private bool CanRedo { get { return UndoCommands.Count > ; } }
}

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

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

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

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

  1. 设计模式学习之命令模式(Command,行为型模式)(12)

    一.命令模式的定义 命令模式属于对象的行为型模式.命令模式是把一个操作或者行为抽象为一个对象中,通过对命令的抽象化来使得发出命令的责任和执行命令的责任分隔开.命令模式的实现可以提供命令的撤销和恢复功能 ...

  2. 关于wp8.1 runtime模式下面的摄像头调用拍照问题和应用生命周期问题

    现在的msdn文档,还找不到详细的wp8.1的摄像头拍照文档,只有一个序列拍照,类似九连拍的文档,而且这文档感觉就是windows8.1搬过来应付的,wp8.1模式,只要有一个地方处理不好,手机就会死 ...

  3. 行为型模式(二) 命令模式(Command)

    一.动机(Motivate) 在我们的现实生活中有很多例子可以拿来说明这个模式,我们还拿吃饺子这个事情来说.我的奶奶说了,今天想吃饺子,发出了命令,然后我奶奶就去看电视去了.我们夫妻俩收到命令就开始和 ...

  4. DesignPattern(五)行为型模式(上)

    行为型模式 行为型模式是对在不同对象之间划分责任和算法的抽象化.行为模式不仅仅关于类和对象,还关于它们之间的相互作用.行为型模式又分为类的行为模式和对象的行为模式两种. 类的行为模式——使用继承关系在 ...

  5. 设计模式(十四):Command命令模式 -- 行为型模式

    1.概述         在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来 ...

  6. 十五、命令(Command)模式--行为型模式(Behavioral Pattern)

    命令模式又称为行动(Action)模 式或交易(Transaction)模式.命令模式把一个请求或者操作封装到一个对象中. 命令模式是对命令的封装.命令模式把发出命令的责任和执行命令的责任分割开,委派 ...

  7. 设计模式14:Command 命令模式(行为型模式)

    Command 命令模式(行为型模式) 耦合与变化 耦合是软件不能抵御变化的根本性原因.不仅实体对象与实体对象之间存在耦合关系,实体对象与行为操作之间也存在耦合关系. 动机(Motivation) 在 ...

  8. NET设计模式 第二部分 行为型模式(16):命令模式(Command Pattern)

    命令模式(Command Pattern) ——.NET设计模式系列之十七 TerryLee,2006年7月 概述 在软件系统中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”.但在某些场合,比 ...

  9. C++设计模式-Command命令模式

    Command命令模式作用:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化:对请求排队或记录请求日志,以及支持可撤销的操作. 由于“行为请求者”与“行为实现者”的紧耦合,使用命令模式 ...

随机推荐

  1. 20175314 实验一 Java开发环境的熟悉

    20175314 实验一 Java开发环境的熟悉 一.实验内容 1.使用JDK编译.运行简单的Java程序: 2.使用IDEA 编辑.编译.运行.调试Java程序. 3.完成实验,撰写实验报告,注意实 ...

  2. tp3.2 phpexcel 简单导出多个sheet(execl表格)

    参考链接:https://blog.csdn.net/u011341352/article/details/70211962 以下是公共类PHPExcel.php文件: // 开始 <?php/ ...

  3. ES6自我总结笔记(阮一峰ES6入门)

    [let和const命令] 1.var的作用域是函数体内,不是块级作用域 2.let是更完美的var,let的变量的作用是块级作用域 3.let声明的全局变量不是全局对象属性,不可以通过window. ...

  4. React-router4 第九篇 Ambiguous Matches 模糊匹配

    https://reacttraining.com/react-router/web/example/ambiguous-matches 看了官方的例子,我准备把阮一峰老师的代码再粘贴一次..!!

  5. NCUAP 利用java自带方法实现导入excel取数据

    final JComponent parent = getModel().getContext().getEntranceUI(); JFileChooser chooser = new JFileC ...

  6. Mybatis 中实体类的编写

    一个实体类对应一个数据表 一个属性对应一个字段 默认情况下类名和属性名都采用 “下划线转驼峰” 的命名方式.但具体采用什么样的命名方式并不重要(方式一致即可),在后面使用这些对象的时候,可以通过 re ...

  7. MD5加密算法的Java版本

    网上搜索Java实现MD5的资料很多,错误的也很多. 之前编写的一个阿里云直播鉴权原理算法需要用到MD5算法,网上找了几个,都是不行,浪费了时间,现在贴一个,做备用. import java.secu ...

  8. 顺序队列(C语言)

    #define Queue_MAX_SIZE 20 #define OK 1 #define ERROR 0 #include <stdio.h> #include <stdlib. ...

  9. ABP框架系列之四十一:(Nuget-Packages-Nuget包)

    Packages ASP.NET Boilerplate is distributed on nuget. Here, a list of all official packages. Abp Cor ...

  10. boost-字符文本处理

    1.lexical_cast 一些常见的数值,字符互转函数: 整型int: itoa()._itoa_s atoi()._ttoi 无符号整型unsigned int: _ultoa_s()._ult ...