使用AOP 使C#代码更清晰

来源:互联网 发布:外国人用什么软件 编辑:程序博客网 时间:2024/05/22 09:05

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形式的编程而无需使用任何“笨重”的框架。
0 0
原创粉丝点击