上篇文章中讨论了以“每个过程”为观察点来处理订单流程。我们定义了一个接口IAction
1: public interface IAction
2: {
3: void DoA();
4: void DoB();
5: void DoC();
6: ....
7: }
还定义了每个过程“拍下商品”、“付钱到支付宝”…
1: public class 拍下商品:IAction
2: {
3: void DoA();
4: void DoB();
5: void DoC();
6: }
7:
8: public class 付款到支付宝:IAction
9: {
10: void DoA();
11: void DoB();
12: void DoC();
13: }
以及还有一个负责将每个“过程”进行串联的管理类ActionManager
1: public class ActionManager
2: {
3: private IAction _action =null;
4:
5: public ActionManager(IAction action)
6: {
7: _action =action;
8: }
9:
10: public void DoSomething()
11: {
12: _action.DoA();
13: _action.DoB();
14: _action.DoC();
15: ...
16: }
17: }
最后展示了调用方式:
1: var actionMgr = new ActionManager(拍下商品);
2: actionMgr.DoSomething();
由于实际项目中呢?我们对于IAction每个“动作”(即DoA、DoB、DoC),都会做一些其他的与业务不怎么相关(如日志)或者说在不影响原先的业务逻辑前提下,我要增加一些业务逻辑(比如业务要求在4点之后客户不能确认收货)。
一开始我们的想法都是比较简单:直接在“确认收货”该过程里面修改逻辑代码!!以及在“每个过程”都添加日志的模块。然后将代码编译成dll文件,做个发布包就OK了。
这样的做法有点违反设计模式的“开闭原则”:“说软件实体(类,模块,函数等)应该可以扩展,但是不可以修改”。
那么有没有比较优美的做法来改变“直接修改类”的问题呢?
优化版做法
在23种设计模式中就有一个符合解决上述问题的解决方案——“装饰者模式”。我的理解就是一个“包装类”,生活中有很多这种例子比如中秋节买的月饼,里面的东西都是差不多的可是在经过盒子的“包装”后让人顿时感觉就“高端 大气 上档次”了。
装饰者模式:自己内部拥有一个业务接口(即IAction),而且自己也实现这个业务接口(IAction)。
代码如下:
1: public void ProcedureWrapper:IAction
2: {
3: private IAction _innerAction=null;
4:
5: public ProcedureWrapper(IAction action)
6: {
7: _innerAction=action;
8: }
9:
10: public void DoA()
11: {
12: //做一些其他的事情;比如4点之后不执行改动作,直接return;
13:
14: try
15: {
16: _innerAction.DoA();
17: }catch
18: {
19: //记录些日志
20: }
21:
22: //改过程完成之后;做一些事情。
23: }
24:
25: public void DoB()
26: {
27: //做一些其他的事情;比如4点之后不执行改动作,直接return;
28:
29: try
30: {
31: _innerAction.DoB();
32: }catch
33: {
34: //记录些日志
35: }
36:
37: //改过程完成之后;做一些事情。
38: }
39:
40: public void DoC()
41: {
42: //做一些其他的事情;比如4点之后不执行改动作,直接return;
43:
44: try
45: {
46: _innerAction.DoC();
47: }catch
48: {
49: //记录些日志
50: }
51:
52: //改过程完成之后;做一些事情。
53: }
54: }
然后我们修改下ActionManager
1: public class ActionManager
2: {
3: private IAction _action = null;
4:
5: public ActionManager(IAction action)
6: {
7: if(action!=null)
8: {
9: _action=ProcedureWrapper(action);
10: }
11: }
12:
13: public void DoSomething()
14: {
15: if(action!=null)
16: {
17: _action.DoA();
18: _action.DoB();
19: _action.DoC();
20: }
21: }
22: }
这样的做法虽然满足了上述的要求,但是有个弊端就是每个方法几乎都被定死了即“DoC前”—>“DoC中”—>“DoC后”。在“DoC前和后”都已经规定死了要做什么,要是有些“过程”我们比较特殊(即我不要记日志,或者记日志的方式不一样了)。
面对这个问题我能想到的有两种做法:一个用接口IMethodBefore,IMethodAfter;另一种做法就是定义两个事件:OnActionBefore、OnActionAfter
事件的做法大家应该都会做的,也是比较用到的毕竟.Net就是以事件为驱动的。这里我就讲下接口的做法
接口定义如下:
1: public interface IMethodBefore
2: {
3: void DoBefore();
4: }
5:
6: public interface IMethodAfter
7: {
8: void DoAfter();
9: }
然后呢我定义一个默认的做法类DefaultMethodAction
1: public class DefaultMethodAction:IMethodBefore,IMethodAfter
2: {
3: public DoBefore()
4: {
5: //做一些通用的事情;如上述的“4点之后不执行该动作”
6: }
7:
8: public DoAfter()
9: {
10: //做一些通用的事情;如上述的“4点之后不执行该动作”
11: }
12: }
DefaultMethodAction用于如果没有其他的业务要求我们就做默认的的动作。
自定义的IMethodBefore,IMethodAfter该如何实现呢?这种实现有很多种方式,这里提供一种我的方式—反射、特性。
特性定义如下:
1: public class BeforeAttribute : Attribute
2: {
3: public IMethodBefore MethodBefore { get; private set; }
4: public BeforeAttribute(IMethodBefore before)
5: {
6: MethodBefore = before;
7: }
8: }
9:
10: public class AfterAttribute : Attribute
11: {
12: public IMethodAfter MethodAfter { get; private set; }
13: public BeforeAttribute(IMethodAfter before)
14: {
15: MethodAfter = before;
16: }
17: }
修改下ProcedureWrapper类代码如下:
1: public class ProcedureWrapper : IAction
2: {
3: private IMethodBefore _defaultBefore = null;
4: private IMethodAfter _defaultAfter = null;
5: private IAction _action = null;
6: /// <summary>
7: /// key:方法名称
8: /// </summary>
9: private Dictionary<string, IMethodBefore> methodBefore = new Dictionary<string, IMethodBefore>();
10:
11: /// <summary>
12: /// key:方法名称
13: /// </summary>
14: private Dictionary<string, IMethodAfter> methodAfter = new Dictionary<string, IMethodAfter>();
15: public ProcedureWrapper(IAction action)
16: {
17: _defaultBefore = new DefaultMethodAction();
18: _defaultAfter = new DefaultMethodAction();
19: _action = action;
20:
21: var methods = _action.GetType().GetMethods();
22: foreach (var methodInfo in methods)
23: {
24: var methodName = methodInfo.Name.ToLower();
25: var attrBefore =methodInfo.GetCustomAttributes(typeof(BeforeAttribute),true);
26: if (attrBefore.Any())
27: {
28: var before = attrBefore[0] as BeforeAttribute;
29: if (before != null) methodBefore.Add(methodName,before.MethodBefore);
30: }
31:
32: var attrAfter =methodInfo.GetCustomAttributes(typeof(AfterAttribute),true);
33: if (attrAfter.Any())
34: {
35: var after = attrBefore[0] as AfterAttribute;
36: if (after != null) methodAfter.Add(methodName,after.MethodAfter);
37: }
38: }
39: }
40: public void DoA()
41: {
42: IMethodBefore before = _defaultBefore;
43: IMethodAfter after = _defaultAfter;
44: const string key = "doa";
45: if (methodBefore.ContainsKey(key))
46: {
47: before = methodBefore[key];
48: }
49:
50: before.DoBefore();
51:
52: try
53: {
54: _action.DoA();
55: }
56: catch (Exception)
57: {
58:
59: throw;
60: }
61:
62: if (methodAfter.ContainsKey(key))
63: {
64: after = methodAfter[key];
65: }
66: after.DoAfter();
67: }
68:
69: public void DoB()
70: {
71: IMethodBefore before = _defaultBefore;
72: IMethodAfter after = _defaultAfter;
73: const string key = "dob";
74: if (methodBefore.ContainsKey(key))
75: {
76: before = methodBefore[key];
77: }
78:
79: before.DoBefore();
80:
81: try
82: {
83: _action.DoB();
84: }
85: catch (Exception)
86: {
87:
88: throw;
89: }
90:
91: if (methodAfter.ContainsKey(key))
92: {
93: after = methodAfter[key];
94: }
95: after.DoAfter();
96: }
97:
98: public void DoC()
99: {
100: IMethodBefore before = _defaultBefore;
101: IMethodAfter after = _defaultAfter;
102: const string key = "doc";
103: if (methodBefore.ContainsKey(key))
104: {
105: before = methodBefore[key];
106: }
107:
108: before.DoBefore();
109:
110: try
111: {
112: _action.DoC();
113: }
114: catch (Exception)
115: {
116:
117: throw;
118: }
119:
120: if (methodAfter.ContainsKey(key))
121: {
122: after = methodAfter[key];
123: }
124: after.DoAfter();
125: }
126: }
然后就是我们的每个过程“用法”
1: public class 拍下商品:IAction
2: {
3: //在这里你可以打上BeforeAttribute或者AfterAttribute,也可以不打上面的两个标签,让其走默认的做法
4: public void DoA()
5: {
6:
7: }
8:
9: public void DoB()
10: {
11:
12: }
13:
14: public void DoC()
15: {
16:
17: }
18: }
我们最终的调用方式还是和原来一样,代码如下:
1: var actionMgr = new ActionManager(拍下商品);
2: actionMgr.DoSomething();