使用AOP 使C#代码更清晰

220 篇文章 18 订阅
140 篇文章 0 订阅

http://blog.csdn.net/yanghua_kobe/article/details/6917228

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
简介
 
如果你很熟悉面向方面编程(AOP),你就会知道给代码增加“切面”可以使代码更清晰并且具有可维护性。但是AOP通常都依赖于第三方类库或者硬编码的.net特性来工作。虽然这些实现方式的好处大于它们的复杂程度,但是我仍然在寻找一种实现AOP的更为简单的方式,来试我的代码更为清晰。我将它们单独移出来,并命名为AspectF。
 
Aspect Oriented Programming (AOP)的背景
“切面”指的是那些在你写的代码中在项目的不同部分且有相同共性的东西。它可能是你代码中处理异常、记录方法调用、时间处理、重新执行一些方法等等的一些特殊方式。如果你没有使用任何面向切面编程的类库来做这些事情,那么在你的整个项目中将会遗留一些很简单而又重复的代码,它将使你的代码很难维护。例如,在你的业务逻辑层有些方法需要被记录,有些异常需要被处理,有些执行需要计时,数据库操作需要重试等等。所以,也许你会写出下面这样的代码。
 
[csharp] view plaincopyprint?
 
     public  bool  InsertCustomer( string  firstName, string  lastName, int  age,  
         Dictionary< string , string > attributes) 
    
         if  ( string .IsNullOrEmpty(firstName))  
             throw  new  ApplicationException( "first name cannot be empty" ); 
         if  ( string .IsNullOrEmpty(lastName)) 
             throw  new  ApplicationException( "last name cannot be empty" ); 
         if  (age < 0) 
             throw  new  ApplicationException( "Age must be non-zero" ); 
         if  ( null  == attributes) 
             throw  new  ApplicationException( "Attributes must not be null" ); 
           
         // Log customer inserts and time the execution 
         Logger.Writer.WriteLine( "Inserting customer data..." ); 
         DateTime start = DateTime.Now; 
           
         try 
        
             CustomerData data = new  CustomerData(); 
             bool  result = data.Insert(firstName, lastName, age, attributes); 
             if  (result == true
            
                 Logger.Writer.Write( "Successfully inserted customer data in "  
                     + (DateTime.Now-start).TotalSeconds + " seconds" ); 
            
             return  result; 
        
         catch  (Exception x) 
        
             // Try once more, may be it was a network blip or some temporary downtime 
             try 
            
                 CustomerData data = new  CustomerData(); 
                 if  (result == true
                
                     Logger.Writer.Write( "Successfully inserted customer data in "  
                         + (DateTime.Now-start).TotalSeconds + " seconds" ); 
                
                 return  result; 
            
             catch  
            
                 // Failed on retry, safe to assume permanent failure. 
                 // Log the exceptions produced 
                 Exception current = x; 
                 int  indent = 0; 
                 while  (current != null
                
                     string  message = new  string (Enumerable.Repeat( '\t' , indent).ToArray()) 
                         + current.Message; 
                     Debug.WriteLine(message); 
                     Logger.Writer.WriteLine(message); 
                     current = current.InnerException; 
                     indent++; 
                
                 Debug.WriteLine(x.StackTrace); 
                 Logger.Writer.WriteLine(x.StackTrace); 
                 return  false
            
        
     }  
 
 
你会看到上面只有两行关键代码,它调用了CustomerData实例的一个方法插入了一个Customer。但去实现这样的业务逻辑,你真的很难去照顾所有的细节(日志记录、重试、异常处理、操作计时)。项目越成熟,在你的代码中需要维护的这些“边边角角”就更多了。所以你肯定经常会到处拷贝这些“样板”代码,但只在这些样板内写少了真是的东西。这多不值!你不得不对每个业务逻辑层的方法都这么做。比如现在你想在你的业务逻辑层中增加一个UpdateCustomer方法。你不得不再次拷贝所有的这些“样板”,然后将两行关键代码加入其中。
 
思考这样的场景,你需要做出一个项目级别的改变——针对如何处理异常。你不得不处理你写的这“上百”的方法,然后一个一个地修改它们。如果你想修改计时的逻辑,做法同样如此。
 
面向切面编程就可以很好地处理这些问题。当你采用AOP,你会以一种很酷的方式来实现它:
[csharp] view plaincopyprint?
 
     [EnsureNonNullParameters] 
     [Log] 
     [TimeExecution] 
     [RetryOnceOnFailure] 
     public  void  InsertCustomerTheCoolway( string  firstName, string  lastName, int  age, 
         Dictionary< string , string > attributes) 
    
         CustomerData data = new  CustomerData(); 
         data.Insert(firstName, lastName, age, attributes); 
    
 
 
这里你需要区分这些通用的东西,像日志记录、计时、重试、验证等这些通常被称为“边边角角”的东西,最重要的是完全与你的“真实”代码无关。这可以使方法将会变得美观而清晰。所有的这些细节都在方法外被处理,并且只是在代码外加上了一些属性。这里,每一个属性代表一个Aspect(切面)。例如,你可以增加“日志记录”切面到任何代码中,只需要增加一个Log属性。无论你使用何种AOP的类库,该类库都能够确保这些“切面”被有效地加入到代码中,当然时机不一,可能是在编译时,也可能是在运行时。
 
有许多AOP类库通过使用编译事件和IL操作允许你在编译时“处理”这些方面,例如PostSharp;而某些类库使用DynamicProxy在运行时处理;某些要求你的类继承自ContextBoundObject使用C#内建特性来supportAspects。所有的这些都有某些“不便”。你不得不使用某些外部库,做足够的性能测试来那些类库可扩展等等。而你需要的只是一个非常简单的方式来实现“隔离”,可能并不是想要完全实现AOP。记住,你的目的是隔离那些并不重要的核心代码,来让一切变得简单并且清晰!
 
AspectF如何来让这一切变得简单!
 
让我展示一种简答的方式来实现这种隔离,仅仅使用标准的C#代码,类和代理的简单调用,没有用到“特性”或者“IL操作”这些东西。它提供了可重用性和可维护性。最好的一点是它的“轻量级”——仅仅一个很小得类。
[csharp] view plaincopyprint?
 
     public  void  InsertCustomerTheEasyWay( string  firstName, string  lastName, int  age, 
         Dictionary< string , string > attributes) 
    
         AspectF.Define 
             .Log(Logger.Writer, "Inserting customer the easy way"
             .HowLong(Logger.Writer, "Starting customer insert" ,  
             "Inserted customer in {1} seconds"
             .Retry() 
             .Do(() => 
                
                     CustomerData data = new  CustomerData(); 
                     data.Insert(firstName, lastName, age, attributes); 
                 }); 
    
 
 
让我们看看它与通常的AOP类库有何不同:
 
(1)     不在方法的外面定义“切面”,而是在方法的内部直接定义。
 
(2)     取代将“切面”做成类,而是将其构建成方法
 
现在,看看它有什么优势:
 
(1)     没有很“深奥”的要求(Attributes, ContextBoundObject, Post build event , IL Manipulation,DynamicProxy)
 
(2)     没有对其他依赖的性能担忧
 
(3)     直接随意组合你要的“切面”。例如,你可以只对日志记录一次,但尝试很多次操作。
 
(4)     你可以传递参数,局部变量等到“切面”中,而你在使用第三方类库的时候,通常不能这么做
 
(5)     这不是一个完整的框架或类库,而仅仅是一个叫做AspectF的类
 
(6)     可能以在代码的任何地方定义方面,例如你可以将一个 for  循环包裹成一个“切面”
 
让我们看看使用这种方案构建一个“切面”有多简单!这个方案中“切面”都是以方法来定义的。AspectExtensions类包含了所有的这些“预构建”的切面,比如:Log、Retry、TrapLog、TrapLogThrow等。例如,这里展示一下Retry是如何工作的:
[csharp] view plaincopyprint?
 
     [DebuggerStepThrough] 
     public  static  AspectF Retry( this  AspectF aspects) 
    
         return  aspects.Combine((work) =>  
             Retry(1000, 1, (error) => DoNothing(error), DoNothing, work)); 
    
       
     [DebuggerStepThrough] 
     public  static  void  Retry( int  retryDuration, int  retryCount,  
         Action<Exception> errorHandler, Action retryFailed, Action work) 
    
         do 
        
             try 
            
                 work(); 
            
             catch  (Exception x) 
            
                 errorHandler(x); 
                 System.Threading.Thread.Sleep(retryDuration); 
            
         } while  (retryCount-- > 0); 
         retryFailed(); 
    
 
 
你可以让“切面”调用你的代码任意多次。很容易在Retry切面中包裹对数据库、文件IO、网络IO、Web Service的调用,因为它们经常由于各种基础设施问题而失败,并且有时重试一次就可以解决问题。我有个习惯是总是去尝试数据库插入,更新,删除、web service调用,处理文件等等。而这样的“切面”无疑让我对处理这样的问题时轻松了许多。
 
下面展示了一下它是如何工作的,它创建了一个代理的组合。而结果就像如下这段代码:
[csharp] view plaincopyprint?
 
     Log(() => 
    
         HowLong(() => 
        
             Retry(() => 
            
                 Do(() => 
                
                     CustomerData data = new  CustomerData(); 
                     data.Insert(firstName, lastName, age, attributes); 
                 }); 
             }); 
         }); 
     }); 
 
 
AspectF类除了压缩这样的代码之外,其他什么都没有。
 
下面展示,你怎样创建你自己的“切面”。首先为AspectF类创建一个扩展方法。比如说,我们创建一个Log:
[csharp] view plaincopyprint?
 
     [DebuggerStepThrough] 
     public  static  AspectF Log( this  AspectF aspect, TextWriter logWriter,  
                 string  beforeMessage, string  afterMessage) 
    
         return  aspect.Combine((work) => 
        
             logWriter.Write(DateTime.Now.ToUniversalTime().ToString()); 
             logWriter.Write( '\t' ); 
             logWriter.Write(beforeMessage); 
             logWriter.Write(Environment.NewLine); 
       
             work(); 
       
             logWriter.Write(DateTime.Now.ToUniversalTime().ToString()); 
             logWriter.Write( '\t' ); 
             logWriter.Write(afterMessage); 
             logWriter.Write(Environment.NewLine); 
         }); 
    
 
 
你调用AspectF的Combine方法来压缩一个将要被放进委托链的委托。委托链在最后将会被Do方法调用。
[csharp] view plaincopyprint?
 
     public  class  AspectF 
    
         /// <summary> 
         /// Chain of aspects to invoke 
         /// </summary> 
         public  Action<Action> Chain = null
         /// <summary> 
         /// Create a composition of function e.g. f(g(x)) 
         /// </summary> 
         /// <param name="newAspectDelegate">A delegate that offers an aspect's behavior.  
         /// It's added into the aspect chain</param> 
         /// <returns></returns> 
         [DebuggerStepThrough] 
         public  AspectF Combine(Action<Action> newAspectDelegate) 
        
             if  ( this .Chain == null
            
                 this .Chain = newAspectDelegate; 
            
             else 
            
                 Action<Action> existingChain = this .Chain; 
                 Action<Action> callAnother = (work) =>  
                     existingChain(() => newAspectDelegate(work)); 
                 this .Chain = callAnother; 
            
             return  this
        
 
 
这里Combine方法操作的是被“切面”扩展方法传递过来的委托,例如Log,然后它将该委托压入之前加入的一个“切面”的委托中,来保证第一个切面调用第二个,第二个调用第三个,知道最后一个调用真实的(你想要真正执行的)代码。
 
Do/Return方法做最后的执行操作。
[csharp] view plaincopyprint?
 
     /// <summary> 
     /// Execute your real code applying the aspects over it 
     /// </summary> 
     /// <param name="work">The actual code that needs to be run</param> 
     [DebuggerStepThrough] 
     public  void  Do(Action work) 
    
         if  ( this .Chain == null
        
             work(); 
        
         else 
        
             this .Chain(work); 
        
    
 
 
就是这些,现在你有一个非常简单的方式来分隔那些你不想过度关注的代码,并使用C#享受AOP风格的编程模式。
 
AspectF类还有其他几个方便的“切面”,大致如下(当然你完全可以DIY你自己的‘切面’)。
 
[csharp] view plaincopyprint?
 
     public  static  class  AspectExtensions 
        
             [DebuggerStepThrough] 
             public  static  void  DoNothing() 
            
       
            
       
             [DebuggerStepThrough] 
             public  static  void  DoNothing( params  object [] whatever) 
            
       
            
       
             [DebuggerStepThrough] 
             public  static  AspectF Delay( this  AspectF aspect, int  milliseconds) 
            
                 return  aspect.Combine((work) => 
                
                     System.Threading.Thread.Sleep(milliseconds); 
                     work(); 
                 }); 
            
       
             [DebuggerStepThrough] 
             public  static  AspectF MustBeNonNull( this  AspectF aspect, params  object [] args) 
            
                 return  aspect.Combine((work) => 
                
                     for  ( int  i = 0; i < args.Length; i++) 
                    
                         object  arg = args[i]; 
                         if  (arg == null
                        
                             throw  new  ArgumentException( string .Format( "Parameter at index {0} is null" , i)); 
                        
                    
                     work(); 
                 }); 
            
       
             [DebuggerStepThrough] 
             public  static  AspectF MustBeNonDefault<T>( this  AspectF aspect, params  T[] args) where  T : IComparable 
            
                 return  aspect.Combine((work) => 
                
                     T defaultvalue = default (T); 
                     for  ( int  i = 0; i < args.Length; i++) 
                    
                         T arg = args[i]; 
                         if  (arg == null  || arg.Equals(defaultvalue)) 
                        
                             throw  new  ArgumentException( string .Format( "Parameter at index {0} is null" , i)); 
                        
                    
                     work(); 
                 }); 
            
       
             [DebuggerStepThrough] 
             public  static  AspectF WhenTrue( this  AspectF aspect, params  Func< bool >[] conditions) 
            
                 return  aspect.Combine((work) => 
                
                     foreach  (Func< bool > condition in  conditions) 
                    
                         if  (!condition()) 
                        
                             return
                        
                    
                     work(); 
                 }); 
            
       
             [DebuggerStepThrough] 
             public  static  AspectF RunAsync( this  AspectF aspect, Action completeCallback) 
            
                 return  aspect.Combine((work) => work.BeginInvoke(asyncresult => 
                
                     work.EndInvoke(asyncresult); completeCallback(); 
                 }, null )); 
            
       
             [DebuggerStepThrough] 
             public  static  AspectF RunAsync( this  AspectF aspect) 
            
                 return  aspect.Combine((work) => work.BeginInvoke(asyncresult => 
                
                     work.EndInvoke(asyncresult); 
                 }, null )); 
            
        
 
 
现在,你已经拥有了一个简洁的方式来隔离那些细枝末节的代码,去享受AOP形式的编程而无需使用任何“笨重”的框架。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值