如何使用C#在控制台上动态构建中间件管道?
如上图所示:我们将会在下面文章上一步一步变形实现出这样的功能。
一、傻瓜式执行演示
首先建立控制台项目,创建Begin() FirstMiddleware() SecondMiddleware() End() 三个函数
1 /// <summary>
2 /// 开始执行前
3 /// </summary>
4 public static void Begin()
5 {
6 Console.WriteLine("begin executing");
7 }
8
9 /// <summary>
10 /// 执行结束
11 /// </summary>
12 public static void End()
13 {
14 Console.WriteLine("end executed");
15 }
16
17 public static void FirstMiddleware()
18 {
19 Console.WriteLine("第一个中间件");
20 }
21 public static void SecondMiddleware()
22 {
23 Console.WriteLine("第二个中间件");
24 }
按照如下顺序执行,并显示结果
1 static void Main(string[] args)
2 {
3 //Console.WriteLine("Hello World!");
4 #region 傻瓜式演示中间件执行方法
5 Begin();
6 FirstMiddleware();
7 End();
8 Console.WriteLine("-------------------");
9 Begin();
10 SecondMiddleware();
11 End();
12 #endregion
13 }
二、封装傻瓜式执行函数
这个Begin() End() 两个方法会执行多次,所以我们可以选择封装一下
1 /// <summary>
2 /// 封装一个执行方法 减少重复代码
3 /// 入参可以传递一个无参数 无返回值函数 用内置委托接受
4 /// </summary>
5 /// <param name="function"></param>
6 public static void CommonExecute(Action function)
7 {
8 Begin();
9 function();
10 End();
11 }
如下调用,执行结果和前文一样
1 #region 封装傻瓜式执行函数
2 CommonExecute(FirstMiddleware);
3 Console.WriteLine("-------------------");
4 CommonExecute(SecondMiddleware);
5 #endregion
三、利用lambda变形简化
这里我们需要做个演变,就是将我们要执行的CommonExecute()函数用lambda表达式包裹为另一个函数的参数传递;
于是创建一个ExtraExecute() 函数
1 public static void ExtraExecute(Action function)
2 {
3 try
4 {
5 Console.WriteLine("try here");
6 function();
7 }
8 catch (Exception)
9 {
10
11 throw;
12 }
13 }
我们将包裹后的CommonExecute()作为入参传递给ExtraExecute()调用,如下所示:
1 ExtraExecute(() => CommonExecute(FirstMiddleware)); // 执行第一个中间件,先执行ExtraExecute() 再执行CommonExecute()
2 Console.WriteLine("-----------");
3 ExtraExecute(() => CommonExecute(SecondMiddleware)); // 执行第二个中间件
通过观察发现CommonExecute() 与ExtraExecute()方法签名一样,所以我们可以调换下两者的执行顺序,如下所示:
1 Console.WriteLine("-----------");
2 // 更改中间件调用顺序 先执行CommonExecute() 再执行ExtraExecute()
3 CommonExecute(() => ExtraExecute(FirstMiddleware));
带参创建中间件调用链
我们刚刚演示了无参的中间件调用,现在给中间件执行体增加入参,新增FirstPipe(string msg, Action
将中间件执行函数包裹成参数传递给下一个执行的函数
1 Action<string> pipe = (msg) => ThirdPipe(msg,
2 (msg1) => SecondPipe(msg1,
3 (msg2) => FirstPipe(msg2, FirstMiddleware)));
4 pipe("Hello World"); // 调用
动态创建中间件管道模型
从带参创建中间件调用链上我们发现,我们无法把这个管道变成动态的,那么如何才能将中间件动态传入以及动态改变调用顺序呢?
通过分析我发现,一个管道执行的最基本单元可以像下面这样定义
1 public abstract class DynamicPipeBase
2 {
3 protected readonly Action<string> _action;
4
5 public DynamicPipeBase(Action<string> action)
6 {
7 this._action = action;
8 }
9 public abstract void Handle(string msg); // 调用执行中间件函数体
10 }
然后定义三个函数继承 DynamicPipeBase()
1 /// <summary>
2 /// 第三个中间件
3 /// </summary>
4 public class ThirdSelfPipe : DynamicPipeBase
5 {
6 public ThirdSelfPipe(Action<string> action):base(action)
7 {
8
9 }
10 public override void Handle(string msg)
11 {
12 Console.WriteLine("Third Pipe Begin executing");
13 _action(msg);
14 Console.WriteLine("Third Pipe End executed");
15 }
16 }
17
18 public class SecondSelfPipe : DynamicPipeBase
19 {
20 public SecondSelfPipe(Action<string> action) : base(action)
21 {
22
23 }
24 public override void Handle(string msg)
25 {
26 Console.WriteLine("Second Pipe Begin executing");
27 _action(msg);
28 Console.WriteLine("Second Pipe End executed");
29 }
30 }
31
32 /// <summary>
33 /// 第一个中间件
34 /// </summary>
35 public class FirstSelfPipe : DynamicPipeBase
36 {
37 public FirstSelfPipe(Action<string> action) : base(action)
38 {
39
40 }
41 public override void Handle(string msg)
42 {
43 Console.WriteLine("First Pipe Begin executing");
44 _action(msg);
45 Console.WriteLine("First Pipe End executed");
46 }
47 }
有了中间件,我们接着只需要构建管道即可,如下所示
1 /// <summary>
2 /// 存储中间件: List<Type> _types;
3 /// 生成中间件调用链: GeneratePipe
4 /// 构建回调:Build
5 /// </summary>
6 public class PipeBuilder
7 {
8 protected readonly Action<string> _mainAction;
9 List<Type> _types;
10 public PipeBuilder(Action<string> mainAction)
11 {
12 _mainAction = mainAction;
13 _types = new List<Type>();
14 }
15 public PipeBuilder AddPipe(Type type)
16 {
17 _types.Add(type);
18 return this;
19 }
20
21 private Action<string> GeneratePipe(int index)
22 {
23 if(index==_types.Count-1)
24 {
25 var finalType= (DynamicPipeBase)Activator.CreateInstance(_types[index], _mainAction);
26 return finalType.Handle;
27 }
28 else
29 {
30 var childHalde= GeneratePipe(index + 1);
31 return ((DynamicPipeBase)Activator.CreateInstance(_types[index], childHalde)).Handle;
32 }
33
34 }
35
36 public Action<string> Build()
37 {
38 return GeneratePipe(0);
39 }
40 }
最终我们在上端调用:
1 #region 动态创建中间件管道模型
2 Action<string> pipeChain = new PipeBuilder(MainAction)
3 .AddPipe(typeof(ThirdSelfPipe)) // 随意添加多个中间件执行单元
4 .AddPipe(typeof(SecondSelfPipe))
5 .AddPipe(typeof(FirstSelfPipe))
6 .Build();
7 pipeChain("Hello World");
8 Console.WriteLine("-------------");
9 #endregion
这样便实现了文章开头展示的效果。