A Taste of Aop from Solving Problems With OOP and Design Patterns (Part i)

zhaozj2021-02-16  73

We first set an example before expanding the problem. In this example I will use the simple logic as simple logic to implement all the functional requirements, which will overseer the core issues we have to solve. The example is a simple calculator class:

Public class calculator {public int add (int x, int y) {return x y;}}

The test code is as follows (you can use NUnit to work with us to this example):

Public void test () {Calculator Calculator = new Calculator (); assert.isnotn; assert.Areequal (5, Calculator.Add (2, 3));}

This class is simple, but if you imagine it as a more complex business handling class, you will face more processing details other than the core functional implementation, such as rights control, audit log, performance Monitoring, buffering processing, transactional environment, etc. For the sake of simplicity, we first add the function of the log log for this class, which will output the call and processing result of each method to the console, as follows:

Public class calculator {public int add (int x, int y) {console.write ("Add ({0}, {1})", x, y); int result = x y; console.writeLine ("= {0} ", result); returnrate;}}

Again, right? Now we need to achieve performance monitoring for this method, as follows:

Public class calculator {public int add (int x, int y) {console.write ("Add ({0}, {1})", x, y); datetime startTime = precisetimer.now; int result = x y TimeSpan ElapsedTime = precisetimer.elapsedsince (start); "[{0}]", ELAPSEDTIME); console.writeLine ("= {0}", result); return result;}}

You should not deport the precisetimer class here. It is just a tool for timing, and there is no big relationship with our core issues. At this point, you have already felt that we have achieved the required features, but in a method, you have stacked different codes for processing all kinds of matters (Aspect! I heard you! :). Although there is no feeling in this simple example, please imagine what happens if we will add a second method to this class:

Public class calculator {public int add (int x, int y) {console.write ("Add ({0}, {1})", x, y); datetime startTime = precisetimer.now; int result = x y TimeSpan ElapsedTime = precisetimer.elapsedsince (start); "[{0}]", ELAPSEDTIME); console.writeLine ("= {0}", result); returnrate;} public int Subtract (int X) , int y) {console.write ("Subtract ({0}, {1})", x, y); datetime starttime = precisetimer.now; int result = x - y; timespan elapsedtime = precisetimer.Elapsedsince (start) Console.write ("[{0}]", ELAPSEDTIME); console.writeLine ("= {0}", result); returnrate;}} The unit test code added at this time is completed as follows:

Public void test () {iCalculator Calculator = new Calculator (); assert.isnotn; assert.arequal (5, Calculator.Add (2, 3)); assert.arequal (5, Calculator.Subtract (8, 3 ));

In both methods, repetitive code has been obvious, this is not a good SMELL - think about if our calculator has 10 methods? If we have another dozens of classes similar to the calculator class? If we have more ways to implement it (permission control, transaction management ...)? In enterprise application development, this can be a problem that he will encounter. For the sake of clarity, we decompose the problem into two parts. The primary problem is confusing the code duty, and the same code logic repeatedly - these issues will lead to various difficulties in development management, code writing and maintenance.

In order to solve the problem of confusion of code duties, we first consider using Decorator design mode to decompose different responsibilities to several Decorator, and use an object constructor (such as Abstract Factory or Factory Method or Builder, etc. It can be possible to assemble the Decorator with the core at runtime. In order to implement Decorator mode, we first need to use the reconstruction of Extract Interface to extract the interface, as follows:

Public interface ics; int Subtract (int x, int y);} public class calculator: iCalculator {public int add (int x, int y) {Console.write ("add ({ 0}, {1}) ", x, y); datetime starttime = precisetimer.now; int result = x y; timespan elapsedtime = precisetimer.elapsedsince (start); console.write (" [{0}] ", ELAPSEDTIME); console.writeLine ("= {0}", result); returnrate;} public int subtract (int x, int y) {console.write ("subtract ({0}, {1})", X , y); DateTime StartTime = precisetimer.now; int result = x - y; timespan elapsedtime = precisetimer.elapsedsince (start); console.write ("[{0}]", ELAPSEDTIME); console.writeline ("= { 0} ", result); Return Result;}} The test code is modified as follows:

Public void test () {iCalculator Calculator = new Calculator (); assert.isnotn; assert.arequal (5, Calculator.Add (2, 3)); assert.arequal (5, Calculator.Subtract (8, 3 ));

Running test code can be sure that the Calculator's behavior is now not affected (this is an important sign of the correct use of reconstruction technology), then we start to peel off the code responsible for monitoring responsibilities and implement it as a Decorator:

PUBLIC CLASS CALCULATOR: ICALCULATOR {Public Int Add (int X, int y) {console.write ("Add ({0}, {1})", x, y); datetime starttime = precisetimer.now; int result = x Y; Timespan Elapsedtime = precisetimer.elapsedsince (start); console.write ("[{0}]", ELAPSEDTIME);

Console.writeLine ("= {0}", result); returnrate;} public int subtract (int x, int y) {console.write ("Subtract ({0}, {1})", x, y) DateTime StartTime = precisetimer.now; int result = x - y; timespan elapsedtime = precisetimer.elapsedsince (start); console.write ("[{0}]", elapsedtime); console.writeline ("= {0}" , RETURN RESULT;}}}

public class CalculatorTimer: ICalculator {public int Add (int x, int y) {DateTime startTime = PreciseTimer.Now; int result = Decoratee.Add (x, y); TimeSpan elapsedTime = PreciseTimer.ElapsedSince (start); return result;} public int Subtract (int x, int y) {DateTime startTime = PreciseTimer.Now; int result = Decoratee.Subtract (x, y); TimeSpan elapsedTime = PreciseTimer.ElapsedSince (start); return result;}}

It can be seen that in response to performance monitoring (in this simple example, we are not as good as it Timer :) Decorator, the code only focuses on the functionality, but will pay the core work to Decoratee (self-donor, don't mind - Caller / Callee, Tester / TESTEE, Assigner / Assigne, Bomber / Bombee ... :) deal with. The decoratee here is also an interface reference for the Icalculator type. We need to set Decoratee when constructing Decorator, which will be implemented in the object factory. Here I am in place for all possible Decorator, which implements a new interface iCalculatorDecorator. In this interface, we declare a semantics that supports reading and writing Decoratee, as follows:

Public interface icsculatorDecorator {void initdecorator (iCalculator Decoratee); iCalculator DecorateE {Get;}}

Then we provide a basic implementation of the interface for all Decorator:

Public Abstract Class CalculatorDecorator: IcalculatorDecorator {Private Icalculator Decorate;

void ICalculatorDecorator.InitDecorator (ICalculator decoratee) {this.decoratee = decoratee;} // FIXED:.! to use implicit interface implementation instead explicit way // so that derived classes could access this property Thanks greatqn ICalculator ICalculatorDecorator.Decoratee public ICalculator Decoratee { Get {return this.decoratee;}}} is then giving birth to the previous CalculatortormTimer:

public class CalculatorTimer: CalculatorDecorator, ICalculator {public int Add (int x, int y) {DateTime startTime = PreciseTimer.Now; int result = Decoratee.Add (x, y); TimeSpan elapsedTime = PreciseTimer.ElapsedSince (start); return result ;} public int Subtract (int x, int y) {DateTime startTime = PreciseTimer.Now; int result = Decoratee.Subtract (x, y); TimeSpan elapsedTime = PreciseTimer.ElapsedSince (start); return result;}}

In order to combine this Decorator that is responsible for performance monitoring, we need an object factory class (it can also create logical decoupling of call code logic with objects):

public class CalculatorFactory {private CalculatorFactory () {} private static Type [] GetObjectGraph () {ArrayList objectGraphTypes = new ArrayList (); // Add decoratee as 1st type to be created

ObjectgraphTypes.add (TypeOf (Calculator)); // and then add all decorators objectgraphtypes.add (typeof (call []) ObjectgraphTypes.toArray (Typeof (Type);

} Public static ics [] objectgraphtypes = getObjectGraph ();

Icalculator result = NULL;

foreach (Type calcType in objectGraphTypes) {ICalculator calcImpl = (ICalculator) Activator.CreateInstance (calcType); if (calcImpl is ICalculatorDecorator) {((ICalculatorDecorator) calcImpl) .InitDecorator (result);} result = calcImpl;} return result;} }

The corresponding modification unit test makes it uses this factory's CreateInstance () method (this is actually the so-called Factory Method in the design mode), as follows:

Public void test () {iCalculator Calculator = CalculatorFactory.createInstance (); assert.isnotnull (Calculator); assert.arequal (5, Calculator.Add (2, 3)); assert.arequal (5, Calculator.Subtract (8, 3));

In this class, we provide a static method createInstance () to return an implementation of Icalculator. In this implementation logic, we first construct an object assembly (Object Graph), in fact, a list of types of Icalculator or iCalculatorDecorator, in order, in order from the inside to outside. Next, a Foreach loop creates each type in sequence according to the constructor, and judges if the newly created type is an icsculatorDecorator. If so, we will have the last iCalculator implementation (ie, Result) is given as Decoratee It uses a combination of objects. When the cycle is completed, the Icalculator implementation referenced by Result is the outermost Decorator, and the entire calling chain is actually constructed by decoratee.xxx () in the method of each Decorator class.

With this improved object structure, we can easily add new Decorator to add new duties as an object, such as the previous logging logic as follows:

Public Class Calculator: CalculatorDecorator, iCalculator {Public Int Add (int x, int y) {console.write ("Add ({0}, {1})", x, y); int result = decoratee.add (x, y); console.writeline ("= {0}", result); returnrate;} public int subtract (int x, int y) {console.write ("subtract ({0}, {1})", X , y); int result = decoratee.subtract (x, y); console.writeLine ("= {0}", result); return fruit;}}

Next, simply add it to the object constructor in the CREATEINSTANCE () method of the object factory:

public class CalculatorFactory {... private static Type [] GetObjectGraph () {ArrayList objectGraphTypes = new ArrayList (); // Add decoratee as 1st type to be createdobjectGraphTypes.Add (typeof (Calculator)); // and then add all decorators objectGraphTypes. Add (CalculatorTimer); ObjectgraphTypes.Add (TypeOf (CalculatorLogger); Return (Type []) ObjectGraphTypes.toArray (Typeof (Type));

PUBLIC Static iCalculator CreateInstance () ...}

However, with my limited design experience, let all Decorator explicitly call Decoratee methods to form an object relationship chain is cumbersome and error-effective (but also the best efficiency). Because if a method implements the corresponding method of Decorate, the entire object chain will be cut off. At the same time, we have also found that as the components interface is increasingly complex, there are more and more methods in the interface, so all of these interface methods are required for each new Decorator, which is not applied to all interface methods. Decorator is a bit cumbersome (such as a buffer strategy Decorator, etc., which is meaningful to a few methods). These phenomena suggest that we can use the Template Method design mode to provide a unified, flexible foundation implementation logic for all Decorator, as follows:

Public Abstract Class CalculatorDecorator: iCalculatorDecorator, iCalculator {Private iCalculator Decorate;

void ICalculatorDecorator.InitDecorator (ICalculator decoratee) {this.decoratee = decoratee;} public ICalculator Decoratee {get {return this.decoratee;}} int ICalculator.Add (int x, int y) {DoPreAdd (ref x, ref y); Int results = decoratee.add (x, y); dopostadd (x, y, ref result); return result;} int icsulator.subtract (int x, int y) {dopResubtract (REF X, REF Y); int result = decoratee.Subtract (x, y); DoPostSubtract (x, y, ref result); return result;} protected virtual void DoPreAdd (ref int x, ref int y) {} protected virtual void DoPostAdd (int x, int y, ref Result) {} Protected Virtual Void DopResubtract (Ref Int X, Ref Int Y) {} Protected Virtual Void DepostSubtract (INT X, INT Y, REF RESULT) {}}

In this Decorator base class based on the Template Methods design mode, we explicitly implement the template logic of all methods of the iCalculator interface, and the variable part of the template is delegated to a number of protected virtual template methods. These template methods will be derived. Class selectable Override (because it is Virtual). Of course, if you want all Decorator to provide a template method for a link, you can also use protected abstract to modify this template method, so that derived classes must implement this method to be compiled. In addition, although we use explicit interfaces on the base class, derived classes can still re-provide the implementation of the interface, which is sufficient to meet various applications (see 13.4.1 and 13.4 of C # language specification. 4). There is also a problem that needs to be solved, ie the mutual collaboration between Decorator is not completed in the current design. Why do you want to collaborate between Decorator? For a simple example, if we need to perform the time executed in the logger, this time is measured by Timer, we need to obtain the time-length information recorded by Timer in the logger implementation (of course, this is required Logger is located in the outer layer of Timer, so when the Timer returns Logger, it is possible to get information on Timer records). To achieve this, an object instance that needs to be combined into all constituent objects is required to introduce a public information container (such as a data dictionary implemented using a HashTable), as a context. We hope that all Decorator can get this shared container, so we first expand the IcalculatorDecorator interface and its implementation:

Public Interface iCalculatorDecorator {Void InitDecorator (iDictionary decoratee, iDictionary context); ics;}}}}}

public abstract class CalcuatorDecorator: ICalculatorDecorator {private ICalculator decoratee; private IDictionary context; public void InitDecorator (ICalculator decoratee, IDictionary context) {this.decoratee = decoratee; this.context = context;} public ICalculator Decoratee {get {return this.decoratee; }}} Public idictionary context {get {return this.context;}}}

Next, we create and set up for the composite object when you create an object in the object factory.

Public Class CalculatorFactory {... public static iCalculator createInstance () {type [] objectgraphtypes = getObjectgraph ();

ICalculator result = null; IDictionary context = new Hashtable (); foreach (Type calcType in objectGraphTypes) {ICalculator calcImpl = (ICalculator) Activator.CreateInstance (calcType); if (calcImpl is ICalculatorDecorator) {((ICalculatorDecorator) calcImpl) .InitDecorator ( Result, context);} RESULT = Calcimpl;} Return Result

In this way, the Context property can be accessed using the context attribute to access the shared storage environment of each Decorator in the entire object combination within Decorator (i.e., the derived class of CalculatorDecorator):

public class CalculatorTimer: CalculatorDecorator, ICalculator {public int Add (int x, int y) {DateTime startTime = PreciseTimer.Now; int result = Decoratee.Add (x, y); TimeSpan elapsedTime = PreciseTimer.ElapsedSince (start); Context [ "Add.ElapsedTime"] = elapsedTime; return result;} public int Subtract (int x, int y) {DateTime startTime = PreciseTimer.Now; int result = Decoratee.Subtract (x, y); TimeSpan elapsedTime = PreciseTimer.ElapsedSince ( Start); context ["subtract.elapsedtime"] = elapsedtime; returnrate;}}

Public Class Calculator: CalculatorDecorator, iCalculator {Public Int Add (int x, int y) {console.write ("Add ({0}, {1})", x, y); int result = decoratee.add (x, y); console.writeLine ("= {0} {1}", result, context ["add.elapsedtime"]); returnrate;} public int subtract (int x, int y) {Console.write ("Subtract ({0}, {1}) ", x, y); int result = decoratee.subtract (x, y); console.writeLine (" = {0} {1} ", Result, Context [" Subtract.ELAPSEDTIME "]); Return Result;}}

This example has developed a certain object structure to this, let us take a look. First, we put forward a problem, that is, in the development of functional needs, it is necessary to take care of the functional needs, it needs to take care of the non-functional needs of different aspects. This makes the implementation of the object method more and more complex, cumbersome, stiff. And difficult to maintain (although it is still optimized from the angle of execution efficiency); Next, we introduce the object structure design mode Decorator to disperse the objects that must be implemented into the interface-based core implementation objects and several modified objects, and Furthermore, the object creation design mode is encapsulated by the construction process of these objects, keeping a loose coupling of the calling code; Finally, we make small corrections and expansion of the formed object structure, allowing them to allow each Decorator through a shared Context communicates with each other. After this design process, we finally formed a more easy to expand, loosely coupled, easier maintenance object model than the initial model, but we also face a problem: if we have a lot of such business components need the same Some of the Decorator, we will still face a lot of repetition code, and these codes cannot be eliminated in object-oriented. What do you mean? Take Logger as an example. If you give hundreds of methods to dozens of components, you will not be a huge development and maintenance work. Why not eliminate object-oriented technology? Because we want to eliminate repetitions at the level of the method, the method is already the scope of the internal package, it is difficult to provide a unified implementation for each method of different types (if you need to have a public in all objects) In the class - Object? Obviously enter the dead blessing ...). So how do this problem solve it (especially in the .NET environment)? I will explore the related mechanisms provided in the CLR in this article to explore the relevant technologies encoded in the .NET environment, including the related technologies in the CLR, including the transpantproxy / realproxy, MarshalByrefObject / MarshalByrefObject / ContextBoundObject, Context Attribute / Context Property / IMESSAGESINK, and more.

转载请注明原文地址:https://www.9cbs.com/read-16485.html

New Post(0)