阅读目录:
这一篇文章我早准备写的,迟迟未写的原因是它过于抽象不太容易表达,也很难掌握;之前对它的理解还处于比较简单的功能性上,但是最近随着对领域驱动设计及架构的研究,设计思想有了一个提升对它的理解也有了一个更清晰的轮廓,所以才敢下手去写,这么好的一篇文章不能搞砸了;
“钝化语句” 简单描述:将基于栈的调用抽象成基于我们自己构建的虚拟运行时调用;
比如我们可以将普通的IF\ELSE调用进行对象化,然后就可以对他们进行面向对象的设计了;能做的事情就太多了,比如将所有的方法放入一个for循环语句组件当中去,它会自动的去循环执行,而不需要我们再去自己写for语句;然后在此基础上进行代码书签抽象对所有的代码片段进行类似逻辑锚点的设定;
更吓人的是可以瞬间将语句组件钝化,其实也就是瞬间冻结然后持久化,在遥远的地方再将它唤醒执行,很可能你的语句在你这台电脑上执行了一半由于你临时有事然后语句被钝化,在另外一台电脑上继续你的工作,是不是很方便;当然它的使用方式多种多样了;
我相信这篇文章绝对让你对 .NET框架设计 感兴趣,框架设计思想其实真的很美,让人陶醉;
美好的一切都要有一个良性的开始,程序的钝化少不了对程序的逻辑保存的功能;有一个连续的调用穿过N个方法,方法一调用方法二,方法二调用方法三,这样的调用层次是根据业务的需求来定的,就好比一个复杂的业务逻辑这样的处理下去合情合理;
那么什么是代码书签呢?其实我们仔细分析一下我们日常所写的代码基本上都是由方法组合而成,不管是实例类还是静态类都是通过方法将彼此联系起来,所有的业务逻辑都是包装在方法的内部处理的,这里的代码书签就是方法的可持久化抽象;
试想一下,我们要想将程序的逻辑流程钝化肯定是少不了对逻辑调用的保存;原本的程序逻辑是线程本地的执行路径,属于.NETCLR直接管理的,依赖于栈的执行,所以我们无法干预其生命周期过程,那么我们只有将它们对象化后才能由我们自己操控;
图1:
上图的意思是说在一个流程的开始到结束基本上三个重要环节,Begin\Processs…\End过程,在每个过程中需要不同的处理逻辑,在图的偏上方,我们有三个ProcessName名称的小方块表示程序的调用顺序,ProcessName1调用ProcessName2调用ProcessName3;
在ProcessName2的上面我们加了一个Bookmark的标记,表示我们这里所说的代码书签,通过代码书签我们就可以记录下本次执行到哪里了,就好比我们在看书的时候都有一个买书时赠送的书签卡,我们看到哪里就把这个书签卡插在那里,当下次要看的时候直接找到这个书签卡继续看;
这里的代码书签跟这个是一样的道理,理论就是这些我们下面通过示例代码来亲身体验一下这种设计模式;
委托是天生的方法标签,通过委托我们完全可以将一个实例的方法直接锚定下来;
【有关对委托的高级应用不太清楚的可以参见本人的这两篇文章:
我们来构造代码书签对象:
/*==============================================================================
* Author:深度训练
* Create time: 2013-08-10
* Blog Address:http://www.cnblogs.com/wangiqngpei557/
* Author Description:特定领域软件工程实践;
*==============================================================================*/
namespace
ProgramComponent
{
using
System;
/// <summary>
/// Program book mark.
/// </summary>
[Serializable]
public
class
ProgramBookmark
{
/// <summary>
/// Mark program book mark.
/// </summary>
/// <param name="name">Mark name.</param>
/// <param name="continueAt">Program continue.</param>
public
ProgramBookmark(
string
name, ProgramBookmarkLocation continueAt)
{
this
.markname = name;
this
.continueAt = continueAt;
}
private
string
markname;
/// <summary>
/// Book mark name.
/// </summary>
public
string
BookmarkName {
get
{
return
markname; } }
private
ProgramBookmarkLocation continueAt;
/// <summary>
/// Continue location.
/// </summary>
public
ProgramBookmarkLocation ContinueAt {
get
{
return
continueAt; } }
/// <summary>
/// Program load data.
/// </summary>
public
object
Payload {
get
;
set
; }
}
/// <summary>
/// Program book mark location.
/// </summary>
/// <param name="resumed">Resumed bookmark.</param>
public
delegate
void
ProgramBookmarkLocation(ProgramBookmark resumed);
}
|
这段代码是对代码书签的抽象,构造函数传入一个代码书签的名称、书签所表示的物理代码锚点,Payload是表示每次执行物理代码时的输入参数;
上面代码看似简单其实很不简单,它的背后隐藏着一个很大的设计思想:
将一块很大的逻辑代码拆成很多零碎的方法片段,很多人可能会觉得设计本身不就这样要求的嘛,那你可能真的没有深入理解代码碎片会后需要对所有的方法参数进行对象化,不管什么方法都会是同样的参数,只有这样才能让书签连续起作用;
下面我们来看一下代码书签有多巧妙,我们来构造一个简单的示例代码,当然你完全可以设计的很复杂很强大,这里毕竟是传递这种设计思想为主;
/*==============================================================================
* Author:深度训练
* Create time: 2013-08-10
* Blog Address:http://www.cnblogs.com/wangiqngpei557/
* Author Description:特定领域软件工程实践;
*==============================================================================*/
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
using
System.Threading.Tasks;
namespace
ConsoleApplication1
{
[Serializable]
public
class
OrderCheckFlows
{
private
IList<ProgramComponent.ProgramBookmark> flowsManager =
new
List<ProgramComponent.ProgramBookmark>();
public
OrderCheckFlows()
{
ProgramComponent.ProgramBookmark bookmarkCheckOrderPrices =
new
ProgramComponent.ProgramBookmark(
"checkPrices"
,
new
ProgramComponent.ProgramBookmarkLocation(CheckOrderPrices));
flowsManager.Add(bookmarkCheckOrderPrices);
}
public
void
StartCheck()
{
do
{
flowsManager[0].ContinueAt(flowsManager[0]);
}
while
(flowsManager.Count > 0);
}
#region business flows
public
void
CheckOrderPrices(ProgramComponent.ProgramBookmark nextCheck)
{
Console.WriteLine(
"checkPrices..."
);
ProgramComponent.ProgramBookmark bookmarkCheckOrderPrices =
new
ProgramComponent.ProgramBookmark(
"checkPrices"
,
new
ProgramComponent.ProgramBookmarkLocation(CheckOrderItems));
bookmarkCheckOrderPrices.Payload =
true
;
//method parameters.
flowsManager.Add(bookmarkCheckOrderPrices);
flowsManager.RemoveAt(0);
}
public
void
CheckOrderItems(ProgramComponent.ProgramBookmark nextCheck)
{
if
((
bool
)nextCheck.Payload)
{
Console.WriteLine(
"checkItems..."
);
}
else
{
Console.WriteLine(
"end check items."
);
}
flowsManager.RemoveAt(0);
}
#endregion
}
}
|
这个类是一个简单的模拟检查订单的一系列的业务流程;
图2:
上图能看见流程顺利执行完毕了,那么我们来解释一下重要的代码片段;
图3:
在第一个流程里面我们构造一个通往下一个流程的 ProgramComponent.ProgramBookmark 对象,如果这里出现关于流程无法继续下去的条件就可以不创建往下执行的代码书签;在第二流程里面我们获取第一个流程设置的参数,这里是一个Bool值,可以用来判断上一个执行是否成功等信息;
上一节我们完成了对代码书签的抽象实现,但是代码还有很多值得抽象设计的地方,上面的代码中最不太理解的地方就是对书签集合的操作上,很不OO;
那么这一节我们将把它改进,形成OO方式的调用,先看一下哪里不太理解;
图4:
第一个地方就是在声明ProgramCompoent.ProgramBookmark集合上,这样写问题太大了,无法进行扩展改进;然后就是在构造函数中,我们使用了很长一段代码来构造一个ProgramCompoent.ProgramBookmark对象,完全可以减少很多;还有就是在StartCheck方法的内部中进行循环调用书签的代码,也很有问题,完全可以封装在内部实现,外部直接一个CurrentProgram属性执行就行了;
那么对这些问题我们其实少一个ProgramCompoent.ProgramBookmark的管理器对象ProgramCompoent.ProgramBookmarkManager对象,它负责管理所有跟ProgramCompoent.ProgramBookmark对象相关的工作;
/*==============================================================================
* Author:深度训练
* Create time: 2013-08-10
* Blog Address:http://www.cnblogs.com/wangiqngpei557/
* Author Description:特定领域软件工程实践;
*==============================================================================*/
namespace
ProgramComponent
{
using
System.Collections.Generic;
/// <summary>
/// Program book mark Manager.<see cref="System.Collections.Dictionary{BookmarkName,ProgramBookmark}"/>
/// </summary>
public
class
ProgramBookmarkManager : Dictionary<
string
, ProgramBookmark>
{
/// <summary>
/// Add programbookmark and instant next programbookmark.
/// </summary>
/// <param name="bookmark"><see cref="ProgramComponent.ProgramBookmark"/></param>
public
void
Add(ProgramBookmark bookmark)
{
base
.Add(bookmark.BookmarkName, bookmark);
}
/// <summary>
/// Remove programbookmark.
/// </summary>
/// <param name="bookmark"><see cref="ProgramComponent.ProgramBookmark"/></param>
public
void
Remove(ProgramBookmark bookmark)
{
base
.Remove(bookmark.BookmarkName);
}
/// <summary>
/// Resume bookmark by bookmarkname.
/// </summary>
/// <param name="bookmarkName">bookmark name.</param>
/// <param name="payload">Continue load.</param>
public
void
Resume(
string
bookmarkName,
object
payload)
{
ProgramBookmark bookmark;
this
.TryGetValue(bookmarkName,
out
bookmark);
if
(bookmark !=
null
)
{
bookmark.Payload = payload;
bookmark.ContinueAt(bookmark);
}
}
}
}
|
书签管理器基本功能还算简单,主要的方法Resume是用来恢复指定的书签的;再来看一下订单检查流程调用;
/*==============================================================================
* Author:深度训练
* Create time: 2013-08-10
* Blog Address:http://www.cnblogs.com/wangiqngpei557/
* Author Description:特定领域软件工程实践;
*==============================================================================*/
namespace
ConsoleApplication1
{
using
System;
using
ProgramComponent;
[Serializable]
public
class
OrderCheckFlows
{
private
ProgramBookmarkManager BookmarkManager =
new
ProgramBookmarkManager();
public
OrderCheckFlows()
{
BookmarkManager.Add(
new
ProgramBookmark(
"checkPrices"
,
new
ProgramBookmarkLocation(CheckOrderPrices)));
}
public
void
StartCheck()
{
BookmarkManager.Resume(
"checkPrices"
,
null
);
}
#region business flows
public
void
CheckOrderPrices(ProgramComponent.ProgramBookmark nextCheck)
{
Console.WriteLine(
"checkPrices..."
);
BookmarkManager.Remove(nextCheck);
BookmarkManager.Add(
new
ProgramBookmark(
"checkItems"
,
new
ProgramBookmarkLocation(CheckOrderItems)));
BookmarkManager.Resume(
"checkItems"
,
true
);
}
public
void
CheckOrderItems(ProgramComponent.ProgramBookmark nextCheck)
{
if
((
bool
)nextCheck.Payload)
Console.WriteLine(
"checkItems..."
);
else
Console.WriteLine(
"end check items."
);
BookmarkManager.Remove(nextCheck);
}
#endregion
}
}
|
是不是比之前的代码好多了,我感觉是好多了,当然还有很大的重构空间;
这里其实已经可以和链式编程的机制挂钩了,我们可以通过给书签管理器添加N个扩展方法来使书签管理器具有跟链式的调用;
要想把所有的调用都拆开来使用松散的方式组合,通过使用书签机制基本上能将所有的方法进行松散组合;那么我们还需要将逻辑语法进行对象化才能做到无死角的松散;
什么叫语句组件,就是将一些原本无法独立的一些逻辑判断、循环之类的语句对象化,形成更具有对象的组件;
试想一下,如果我们将所有的这些逻辑语法对象化后我们的代码中还有精密耦合的代码吗?就算有也应该会很少,是不是很神奇;
其实对 企业应用架构 中的 规约模式 有所了解的人应该会比较熟悉这一节的内容,跟规约模式很像,但不是一个东西,侧重点不同;语句组件全面的概念是将所有的调用都对象化,包括一些输出、输入、网络调用等等,这样才是全部的语句组件定义,还记得我们上面的订单检查对象嘛,那个也是语句组件之一;
我们来构造可恢复语句组件对象;
ProgramComponent.LanguageComponent.LanguageComponent类代码:
/*==============================================================================
* Author:深度训练
* Create time: 2013-08-10
* Blog Address:http://www.cnblogs.com/wangiqngpei557/
* Author Description:特定领域软件工程实践;
*==============================================================================*/
namespace
ProgramComponent.LanguageComponent
{
using
System;
[Serializable]
public
abstract
class
LanguageComponent
{
public
abstract
void
Run(ProgramBookmarkManager mgr);
}
}
|
ProgramComponent.LanguageComponent.LanguageComponentBlock类代码:
/*==============================================================================
* Author:深度训练
* Create time: 2013-08-10
* Blog Address:http://www.cnblogs.com/wangiqngpei557/
* Author Description:特定领域软件工程实践;
*==============================================================================*/
namespace
ProgramComponent.LanguageComponent
{
using
System.Collections.Generic;
public
class
LanguageComponentBlock : LanguageComponent
{
List<LanguageComponent> statements =
new
List<LanguageComponent>();
public
List<LanguageComponent> Statements
{
get
{
return
statements; }
}
public
void
AddLangugateComponent(LanguageComponent lc)
{
statements.Add(lc);
}
public
override
void
Run(ProgramBookmarkManager mgr)
{
}
}
}
|
ProgramComponent.LanguageComponent.IfElseLanguageComponent类代码:
/*==============================================================================
* Author:深度训练
* Create time: 2013-08-10
* Blog Address:http://www.cnblogs.com/wangiqngpei557/
* Author Description:特定领域软件工程实践;
*==============================================================================*/
namespace
ProgramComponent.LanguageComponent
{
using
System;
using
System.Linq;
using
System.Linq.Expressions;
public
class
IfElseLanguageComponent : LanguageComponentBlock
{
public
Func<
bool
> Exp {
get
;
set
; }
public
override
void
Run(ProgramBookmarkManager mgr)
{
if
(Exp())
base
.Statements[0].Run(mgr);
else
base
.Statements[1].Run(mgr);
}
}
}
|
检查流程代码,OrderCheckFlows\OrderSubmitFlows类代码:
/*==============================================================================
* Author:深度训练
* Create time: 2013-08-10
* Blog Address:http://www.cnblogs.com/wangiqngpei557/
* Author Description:特定领域软件工程实践;
*==============================================================================*/
namespace
ConsoleApplication1
{
using
System;
using
ProgramComponent;
using
ProgramComponent.LanguageComponent;
[Serializable]
public
class
OrderCheckFlows : LanguageComponent
{
private
ProgramBookmarkManager BookmarkManager =
new
ProgramBookmarkManager();
public
OrderCheckFlows(ProgramBookmarkManager bookmarkManager)
{
this
.BookmarkManager = bookmarkManager;
BookmarkManager.Add(
new
ProgramBookmark(
"checkPrices"
,
new
ProgramBookmarkLocation(CheckOrderPrices)));
}
public
override
void
Run(ProgramBookmarkManager mgr)
{
this
.BookmarkManager = mgr;
StartCheck();
}
public
void
StartCheck()
{
BookmarkManager.Resume(
"checkPrices"
,
null
);
}
#region business flows
public
void
CheckOrderPrices(ProgramComponent.ProgramBookmark nextCheck)
{
Console.WriteLine(
"checkPrices..."
);
BookmarkManager.Remove(nextCheck);
BookmarkManager.Add(
new
ProgramBookmark(
"checkItems"
,
new
ProgramBookmarkLocation(CheckOrderItems)));
BookmarkManager.Resume(
"checkItems"
,
true
);
}
public
void
CheckOrderItems(ProgramComponent.ProgramBookmark nextCheck)
{
if
((
bool
)nextCheck.Payload)
Console.WriteLine(
"checkItems..."
);
else
Console.WriteLine(
"end check items."
);
BookmarkManager.Remove(nextCheck);
}
#endregion
}
[Serializable]
public
class
OrderSubmitFlows : LanguageComponent
{
private
ProgramBookmarkManager BookmarkManager =
new
ProgramBookmarkManager();
public
OrderSubmitFlows(ProgramBookmarkManager bookmarkManager)
{
this
.BookmarkManager = bookmarkManager;
BookmarkManager.Add(
new
ProgramBookmark(
"CheckSubmitPrices"
,
new
ProgramBookmarkLocation(CheckSubmitPrices)));
}
public
override
void
Run(ProgramBookmarkManager mgr)
{
this
.BookmarkManager = mgr;
StartCheck();
}
public
void
StartCheck()
{
BookmarkManager.Resume(
"CheckSubmitPrices"
,
null
);
}
#region business flows
public
void
CheckSubmitPrices(ProgramComponent.ProgramBookmark nextCheck)
{
Console.WriteLine(
"CheckSubmitPrices..."
);
BookmarkManager.Remove(nextCheck);
BookmarkManager.Add(
new
ProgramBookmark(
"CheckSubmitItems"
,
new
ProgramBookmarkLocation(CheckSubmitItems)));
BookmarkManager.Resume(
"CheckSubmitItems"
,
true
);
}
public
void
CheckSubmitItems(ProgramComponent.ProgramBookmark nextCheck)
{
if
((
bool
)nextCheck.Payload)
Console.WriteLine(
"CheckSubmitItems..."
);
else
Console.WriteLine(
"end check CheckSubmitItems."
);
BookmarkManager.Remove(nextCheck);
}
#endregion
}
}
|
调用代码:
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
using
System.Threading.Tasks;
namespace
ConsoleApplication1
{
using
ProgramComponent;
using
ProgramComponent.LanguageComponent;
class
Program
{
static
void
Main(
string
[] args)
{
ProgramBookmarkManager bookmarkManager =
new
ProgramBookmarkManager();
OrderCheckFlows orderCheckFlow =
new
OrderCheckFlows(bookmarkManager);
OrderSubmitFlows submitCheckFlow =
new
OrderSubmitFlows(bookmarkManager);
IfElseLanguageComponent languageComponent =
new
IfElseLanguageComponent();
languageComponent.Exp = () => {
return
true
; };
languageComponent.AddLangugateComponent(orderCheckFlow);
languageComponent.AddLangugateComponent(submitCheckFlow);
languageComponent.Run(bookmarkManager);
Console.ReadLine();
}
}
}
|
一切都已经被对象化,我们来看一下逻辑;
图5:
这里的返回值决定了后面要执行的语句组件的路径,如果是true,则应该检查OrderCheckFlows流程;
图6:
如果是false,则应该检查OrderSubmitFlows流程;
图7:
可恢复语句对象模型基本构造完成,当然复杂的问题还需要仔细的去分析设计,这里只是一个简单的示例;
跟代码书签管理器一个道理,这里我们也可以实现一个LanguageComponentManager来对LanguageComponent管理,当然也要看需要不需要;可恢复语句管理器其实有很多文章可以做,因为它是所有语句组件的中心,这对于后面的持久化有很大的用处;
//由于内容比较多且相当抽象,下一篇文章介绍;
所有的语句代码都已经被对象化,但是在运行时需要一个中心来管理这些被对象化的语句组件,因为我们要脱离对栈的依赖;一组语句组件是单个示例流程的一部分,但是我们可能会存在很多一起并行运行的流程,所以这是必须要提供的运行时;
//由于内容比较多且相当抽象,下一篇文章介绍;
领域驱动设计在使用规约模式的时候会存在动态配置的需求,可以参见这里的语句组件模型,让规约最大化的提供配置;
//由于内容比较多且相当抽象,下一篇文章介绍;
//由于内容比较多且相当抽象,下一篇文章介绍;
//由于内容比较多且相当抽象,下一篇文章介绍;
示例DEMO地址:http://files.cnblogs.com/wangiqngpei557/ConsoleApplication3.zip
作者:王清培
出处:http://www.cnblogs.com/wangiqngpei557/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。