Aspect-Oriented Programming Enables Better Code Encapsulatio

发表于:2007-06-30来源:作者:点击数: 标签:
SUMMARY Aspect-oriented Programming (AOP), a paradigm invented at Xerox PARC in the 1990s, lets the developer better separate tasks that should not be inextricably tangled, such as mathematical operations and exception handling. The AOP app
SUMMARY Aspect-oriented Programming (AOP), a paradigm invented at Xerox PARC in the 1990s, lets the developer better separate tasks that should not be inextricably tangled, such as mathematical operations and exception handling. The AOP approach has a number of benefits. First, it improves performance because the operations are more suclearcase/" target="_blank" >ccinct. Second, it allows programmers to spend less time rewriting the same code. Overall, AOP enables better encapsulation of distinct procedures and promotes future interoperation.
--------------------------------------------------------------------------------

hat is it about software engineers that make them wish they were hardware engineers? Since the invention of the function, programmers have spent most of their time (and most of their employers@# money) attempting to design systems that are merely snap-together models of parts built by others, arranged in unique shapes and draped in pleasing colors. Functions, templates, classes, components, and so on, ad infinitum, are all attempts by software engineers to build themselves "Software Integrated Circuits" just like the hardware designers@# electronic analogs.
I blame Lego. That satisfying click that you get when two bricks (aka components) snap together is addictive and has driven many a programmer to invent one new mechanism of encapsulation and reuse after another. The latest such advance is called aspect-oriented programming (AOP). At its core, AOP is a way of laying components, one on top of the other, to achieve levels of reuse not available in other kinds of component-based development. This layering happens on the call stack between client and object, and the effect is to create a specific environment for your object. This environment is chiefly what an AOP programmer is after, as you@#ll see as you read through this article.
There are two parts to the code sample provided with this article: the COM part and the Microsoft? .NET part. The COM part creates an infrastructure for adding aspects to COM objects, provides a UI to configure a class@#s aspects, and provides a sample aspect implementation built on the plumbing we provide. The .NET part shows how to use the infrastructure built into .NET to do the same thing as the COM version did, but with less code and more options. It also provides a sample aspect to fit into that infrastructure. We will describe all of the code later in the article.

What is Aspect-oriented Programming?
Normally, objects are "glued" together using lines of code. Create this object. Create that object. Set a property on that object whose value is this object. Sprinkle in some user data. Stir. Execute when the runtime reaches 450 degrees. The problem with hooking together components in this manner is that you@#re going to be spending a lot of time writing the same bits of code when it comes to implementing the individual methods. It goes something along these lines: log this method to a file for debugging, run a security check, start a transaction, open a database connection, remember to catch C++ exceptions or Win32? structured exceptions so that they can be translated into COM exceptions, and validate parameters. And let@#s not forget you have to remember to tear down at the end of the method whatever it was you set up at the beginning.
The reason that this kind of duplication often occurs is that developers are trained to design systems based on the nouns in software press releases. If you@#re building a banking system, you@#ve got an Account class and a Customer class, both of which gather the details necessary to be uniquely themselves in one place, but both of which have the need, on a per-method basis, to do logging, security checking, transaction management, and so on. The difference is that the logging and such are aspects of the system that are orthogonal to the specific application. Everyone needs them. Everyone writes the code. Everyone hates it.
Well, not everyone... Oh, everyone needs to use these services, and everyone hates to write duplicate code, but not everyone needs to write the code. For example, COM+ and .NET programmers have something called attributed programming, also known as declarative programming. This allows a programmer to decorate a type or a method with an attribute that declares the need for a service to be provided by a runtime. Some of the services provided by COM+, for example, include role-based security, Just-in-Time activation, distributed transaction management, and marshaling. When calling the method, the runtime stacks up a set of objects (called interceptors if you@#re a COM+ programmer, or message sinks if you@#re using .NET) that get between the client and the server to provide the service on each method, requiring no code to be written by the component developer. This is the simplest form of aspect-oriented programming, a paradigm invented in the 1990s at Xerox PARC by Gregor Kiczales (see http://www.parc.xerox.com/csl/groups/ sda/publications/papers/Kiczales-ECOOP97/for-web.pdf).
In the realm of AOP, COM+ interceptors are aspects that are associated with components via metadata. The runtime uses the metadata to compose the stack of aspects, typically at object creation time. When the client calls a method, the particular aspects each get a turn at processing the call and performing their service, until finally the object@#s method is called. On the return trip, each aspect is given a chance to unwind. In this way, you can factor those same lines of code that you find yourself writing in each method of each of your components into aspects, and let the runtime stack them up. This set of aspects work together to provide a context for the component@#s method to execute. The context provides the method implementation in an environment where its actions take on a special meaning.


Figure 1 Object Nestled Safely in a Context

For example, Figure 1 shows an object nestled safely in a context that is providing error propagation, transaction management, and synchronization. Exactly as a Win32 console application program can assume a context where a console exists and calls to printf will be shown on it, an AOP object can assume that a transaction has been established and calls to the database will be part of that transaction. If there were any problems setting up these services (for example there were no resources available to establish a transaction) the object would never be called, saving it the need to worry about that, too.

General-purpose AOP
While COM+ provides most of the services needed of AOP, it lacks one very important detail needed to use it as a general-purpose AOP environment: the ability to define custom aspects. For example, if role-based security doesn@#t do it for you, you can@#t implement role-playing-based security (as much as you may want vampires to guard your objects). If programmers had that ability, many COM idioms could be implemented with an AOP framework. Figure 2 provides a short list of examples.

Designing an Aspect Framework
Of course, once such a framework was conceived, we had to build it. We wanted this framework to have the following features:

A runtime for stringing aspects together between a client and an object.

User-defined aspects implemented as COM components.

Metadata descriptions of which aspects were associated with each COM component, just like the COM+ Catalog.

A method that clients can use to activate components with the aspects in place.

The idea behind our AOP framework is simple. The key to it is interception and delegation. The art of interception lies in making the caller believe that the interface pointer that it holds is pointing to the object it requested, whereas in reality it is a pointer to an interceptor obtained via one of the activation techniques described later in the article. It@#s the interceptor@#s job to implement the same interfaces as the target component, but to delegate all calls through the stack of aspects associated with the component. When a method is called, the interceptor will give each aspect the opportunity to pre- and post-process the call, as well as the ability to propagate or cancel the current call.
The AOP framework performs two distinct steps, component activation and method invocation. In component activation, the AOP framework builds the stack of aspect implementation objects and returns a reference to an interceptor instead of a reference to the actual object. In method invocation, when the caller makes method calls on the interceptor, the interceptor delegates the calls to all the registered aspects for preprocessing of [in] and [in,out] arguments on the call stack, and delivers the actual call to the object. Then it delivers the call to the aspects for post-processing by passing the return value of the call that the component returned, the [in,out] and [out] arguments on the call stack.

Aspects as COM Objects
In our AOP framework, an aspect is a COM class that implements the IAspect interface shown in Figure 3. The framework calls the IAspect::PreProcess method of all the specified aspects before delivering the method call to the actual underlying component instance (referred to as delegatee from here on). It passes the identity of the delegatee, IID of the interface, name of the method, vtbl slot at which the method occurred, and an enumerator over the [in] and [in,out] arguments to the aspect. If an aspect returns a failure HRESULT from PreProcess, the framework does not deliver the call to the delegatee, effectively cancelling the call.
Upon a successful return from the preprocessing by the aspects, the framework then delivers the actual call to the delegatee. Regardless of the return HRESULT from the delegatee, the framework then calls the IAspect::PostProcess method, passing the HRESULT returned by the delegatee and all the parameters as the PostProcess method, except that this time the enumerator is built over the [out], [in,out] and [out,retval] arguments.
Figure 4 shows how to write a call-tracing aspect that traces all the caller-supplied arguments passed to the delegatee@#s method.
Now that we have a framework to call aspects and an aspect to play with, we need to have a mechanism for stringing them together. We@#ll do that as the object is activated.

Object Activation
In spite of the fact that we@#re stacking an arbitrary number of aspects between the client and the object, the client should be able to create the object and call methods on it in the same fashion as it does without interception. Unfortunately, COM doesn@#t support arbitrary extensibility code injected into its chief activation API, CoCreateInstance, without some pretty fancy hackery (which is what Microsoft Transaction Services had to do until it was integrated into the COM plumbing and renamed COM+). However, COM does provide an activation API that@#s fully extensible: GetObject in Visual Basic? (or CoGetObject if you@#re a C++ programmer). We built our AOP activation code based on this API using a custom moniker.
A COM moniker is a piece of code that translates an arbitrary string (called a display name) into a COM object, whether that means creating a new one, digging one out of a file, or downloading it from the moon. Our AOP moniker takes the metadata describing the aspects associated with the class in question, creates an instance of the class, builds the stack of aspects, hooks them all up via an AOP interceptor, and then hands the interceptor back to the client. Here@#s an example:

Private Sub Form_Load()
    Set myfoo = GetObject("AOActivator:c:\AopFoo.xml")
    myfoo.DoSomethingFooish
End Sub

Notice that, except for obtaining the instance of Foo, the client doesn@#t need to do anything special to use the component. It still implements the same interfaces and, even more importantly, it still has the same semantics despite the fact that the AopFoo.xml file could associate any number of aspects with this particular instance of Foo.
Implementing a custom COM moniker is somewhat of a black art, mostly involving intimate knowledge of OLE trivia from bygone days. Luckily, the vast majority of the implementation is boilerplate and the COM community has long ago set down the basic implementation of monikers into an ATL class called CComMoniker. (The COM moniker framework is available at http://www.sellsbrothers.com/tools.) Using the framework, all we really had to worry about was implementing ParseDisplayName, which is a boring method that parses the custom display name syntax, and BindToObject, the part of the moniker that allows us to activate the COM object indicated by the display name provided by the client (see Figure 5).
Notice that the code in Figure 5 doesn@#t show the difficult part¡ªcreating and initializing the interceptor. What makes this difficult is not the interceptor itself, but what the interceptor has to do. Remember, for our generic AOP framework to function generically, it must be able to respond to QueryInterface with the exact same set of interfaces as any component being wrapped. And the interface returned must be able to take the call stack provided by the client for each method, pass it to all of the aspects, and pass it along to the component itself, leaving the arguments intact¡ªno matter how many there are or what types they are. This is a difficult job involving lots of __declspec(naked) and ASM thunks.
Luckily, because the COM community is a mature one, we can again stand on the shoulders of giants and make use of the Universal Delegator (UD), a COM component built by Keith Brown to perform this very task. Keith described his UD in the two part series in MSJ called "Building a Lightweight COM Interception Framework, Part I: The Universal Delegator," and Part II: "The Guts of the UD". We used Keith@#s UD to implement our AOP framework, which reduces the "magic" part of the BindToObject implementation to the code in Figure 6.

原文转自:http://www.ltesting.net