打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
PostSharp Blog | Aspect
Aspect-Oriented Programming vs Dependency Injection
by Gael Fraiteur on 十二月 9, 2010Opinion
In myresponse to Anders Hejlsberg spreadingFUD about aspect-oriented programming, I mentioned the common confusion that Aspect-Oriented Programming (AOP) and Dependency Injection (DI) are competing concepts. In this post, I will further clarify the differences and similarities between AOP and DI.
The timing seems perfect, because Dino Esposito just published the article:Aspect-Oriented Programming, Interception and Unity 2.0 in the December 2010 issue of MSDN Magazine. This is a great article and I highly recommend anyone involved in .NET development to read it. Like most dependency-injection frameworks, and several general frameworks (WCF,ASP.NET MVC), Unity offers an AOP-like feature: interceptors.
What is Dependency Injection?
Dependency Injection is a design pattern that strives to reduce explicit dependencies between components.
One of the benefits of reducing dependencies (moving from a tightly coupled architecture to a loosely coupled architecture) is the reduction in the level of awareness components have of each other, thereby achieving a betterseparation of concerns by implementing the “program to an interface, not to an implementation” design principle.
A common misinterpretation of this design principle among C# and Java developers is that clients should consume interfaces (the thing you declare with the interface keyword) instead of classes. I prefer the following wording: “program to a contract, not to an implementation”. The contract of a class is composed of its public members, augmented by their documentation (plus their formal preconditions, post-conditions and invariants, if applicable). To be even more precise: “program to a documentation, not to an implementation”. So if your aim is just a better separation of concerns, you don’t really need interfaces, you just need the public, internal, protected and private keywords. It’s important to note that in object-oriented design, interfaces are not the silver bullet. Not all framework designers follow the same philosophy: the Java class library relies more on interfaces than that of .NET, probably because .NET designers had negative experience with the all-interface approach of COM.
Programming to an interface (instead of a class) is useful when your component has to talk to many implementations of the interface. There are innumerable cases where this is useful (after all, we all want our architecture to be extensible), but let’s face it: in as many cases, when our component needs to talk to another, we know perfectly well which implementation we have in mind, and there will never be a second implementation running side-by-side. So why use an interface here?
There are two cases when you want to program to an interface even if you know you’ll always have a single implementation:
You depend on a component that has not been delivered yet and need to start programming – and, more importantly, testing your code – right now. You need to simulate (mock) this component until it is delivered, only for the purpose of testing.
You depend on a component that has a persistent state (such as a database), real-world effect (such as a credit card processing system) or simply depend on a slow or unreliable resource (web service). You need to substitute these components with an implementation of your own, only for the purpose of testing.
In these situations (which all happen to be related to testing), it’s good that components are not wired together in source code – even if we know how they will be wired together in the final setup. So how do we wire these components together? This is where Dependency Injection comes into play.
With the DI design pattern, you instruct a “container” how the objects should be wired together, and you ask the container to wire the objects. Indeed, most of the time, you don’t create new objects using the class constructor, but using a factory method of the container. The container would figure out which implementation you need to be wired to, and would return it to you.
Aspect-Oriented Programming
Dependency injection addresses the problem of wiring loosely-coupled components together. Aspect-oriented programming (AOP) solves a very different issue: taking a reality where some features (such as transaction handling or auditing) affect virtually all business requirements, and expressing this complex reality in a compact way, with minimal duplication of code (DRI principle). When we think of auditing, we want to look in a single source file. When we see a piece of code related to customer management, we don’t want to see auditing code. As you know, this is impossible to achieve with plain object-oriented programming, and that’s why aspect-oriented programming was invented.
Aspect-oriented programming allows you to specify an implementation pattern (using source code, not natural language) into a so-called aspect, and to apply the aspect to base code, typically to business or UI code.
Similarities and Differences between DI and AOP
There are some similarities in the objectives of DI and AOP:
Both achieve a loosely coupled architecture.
Both achieve a better separation of concerns.
Both offload some concerns from the base code.
However, DI and AOP differ significantly in situations where they are found to be useful:
DI is good when you have a dependency on a component, you just don’t know to which implementation.
AOP is good when you need to apply a common behavior to a large number of elements of code. The target code is not necessarily dependent on this behavior to be applied.
Dynamic Proxies
How did dependency injection ever become associated with aspect-oriented programming? It’s simply that the DI pattern makes it easy to add behaviors to components. Here’s how: when you ask a dependency from the container, you expect to receive an implementation of an interface. You can probably guess which implementation you will receive, but since you play the game, you just program to the interface. And you’re right. If you ask the container to add a behavior (for instance tracing) to the component, you will not receive the component itself; instead, you will receive a dynamic proxy. The dynamic proxy stands between your code and the component, and applies the behaviors you added to the component.
Because DI frameworks have the ability to put behaviors between the client and the implementation of a component, DI has been identified as one of the technologies able to deliver AOP.
There are two technologies that can deliver dynamic proxies: dynamic code generation (using System.Reflection.Emit, typically), and remoting proxies (seeRealProxy).
Please readDino Esposito’s article in MSDN Magazine for details about dynamic proxies.
Proxy-Based AOP: The Good
What I love about the proxy-based approach is how AOP blends into the DI concept. DI lets you configure how the container should build the components by injecting dependencies, setting properties and calling initialization methods – you can also inject aspects at the interface of components. You can configure aspects just as other dependencies in an XML file or using C# code, just as if the issue of building and assembling components were unified.
The ability to add aspects just by changing a configuration file may prove very useful in production. Suppose there’s some issue somewhere and you want to trace all accesses to a specific component. You can do that without recompiling the application. This may be a decisive advantage in formal environments, where it can take months to go from a bug report to the deployment of a fix.
Proxy-Based AOP: The Bad
Proxy-based AOP, as implemented by DI frameworks, has serious limitations:
It only works on the surface of components, exposed as an interface. It does not work for all methods that are internal to the component, whether public or private, static or instance. Remember that a component is an application part, and generally includes several classes.
It only works when the client code got a reference of the component from the container. Aspects will not be applied if the component invokes itself, or if the component is invoked without the intermediary of the container. This can be seriously misleading!
Since aspects are applied at runtime, there is no possibility for build-time verification. Build-time AOP frameworks such as PostSharp will emit compilation errors if the aspect is used incorrectly, and will even provide the aspect developer with different ways to verify, at build time, that the aspect has been used in a meaningful way (for instance, don’t add a caching aspect to a method that returns a stream, or to a method that already has a security aspect).
It is difficult to figure out (a) which aspects are applied to the piece of code you’re working on and (b) to which pieces of code the aspect you’re working on has been applied. Full frameworks such as PostSharp or AspectJ can display this information inside the IDE, substantially improving the understandability of source code.
Run-time AOP has higher runtime overhead than build-time frameworks. This may be of minor importance if you only apply aspects to coarsely-grained components, but just generating dynamic proxies has a significant cost if you have hundreds of classes.
Dynamic proxies are always a side feature of dependency injection framework and do not implement the full vision, even when technically feasible. They are mostly limited to method interception, which hardly compares to what AspectJ and PostSharp have to offer.
Proxy-Based AOP: The Ugly
There’s always a risk when a practice becomes mainstream: it starts being used just for the sake of itself, because it makes you feel safe. This is when a practice becomes a dogma.
It clearly happened to test-driven development, which suddenly imposed upon the community to write tests at meaningless levels of granularity. And thus so with dependency injection, which tends to be used to isolate classes from each other. Remember that dependency injection had been initially proposed to isolate components, i.e. parts of applications of rather coarse granularity. I am concerned that we are ‘forced’ (by social pressure) to write more complex applications just to meet testing ‘requirements’ that do not result in higher quality. Remember that unit testing is an instrument, not an objective. The ultimate objective is quality of your application during its whole lifetime.
Don’t get me wrong, I am not suggesting that you should not write unit tests. I am concerned that religiously following a practice may actually degrade the quality of your code.
The same issue exists with proxy-based AOP. If DI frameworks made you excited for the power of AOP, you may be tempted to restructure your code just to use more of this proxy-based stuff. Don’t. Adding aspects to the surface of components using proxies is simple and elegant. If you are making your code DI-friendly just because you need aspects, you are probably doing something wrong. The principal mantra of AOP is that you should not change your base code. If you are, you should probably look at a real AOP framework such as PostSharp or AspectJ.
Conclusion
Aspect-Oriented Programming and Dependency Injection are very different concepts, but there are limited cases where they fit well together. In these situations, using the AOP facility of a DI framework makes perfect sense. For other cases you have two options: abuse from dynamic proxies (a non-solution), or use of a dedicated AOP framework.
This is not just me, not just PostSharp, and not just .NET. The same dilemma hangs over Java programmers, who have tochoose between the proxy-based Spring AOP and the build-time AspectJ (read the summary of Ramnivas Laddad ‘s talk on this topic).
AOP and DI are not competing technologies. You can use both in the same project. What’s important is to understand what each technology was designed for, and to focus on the quality of your code.
Happy PostSharping!
-gael
Tweet
相关文章
Dependency Injection NaturallyDependency Injection NaturallyDependency Injection Killed by Simon InceDependency Injection Killed by Simon InceMicroContainer: a dependency injection framework for... the .NET Micro Framework!MicroContainer: a dependency injection framework for... the .NET Micro Framework!
评论 (3)
Steve Degosserie
2010/12/12 22:17:30
Hi Ga?l,
Here are 2 good links explaining the difference between IoC & DI, and why DI is a bad term:
http://hammett.castleproject.org/?p=337
www.betaversion.org/~stefano/linotype/news/38/
IoC is just good OO design, DI is just a particular pattern that focus (too much) on the injection story.
Also, 2 very important principles of OO are message passing between objects & encapsulation. As we all know, the public members of a class constitutes its contract. Dynamic proxies might not be the most powerfull solution for AOP but at least they respect the concept of encapsulation => they intercept messages going in & out of an object. Applying aspects inside a class on its private / static members is pure evil, as you might be breaking invariants of the class, and you're basically ignoring encapsulation (IMHO, this is probably why Anders doesn't want to introduce such a potentially risky technique as basic feature of the C# language).
So, I'm not sure how you draw the line between 'component' and 'class' but it's quite likely that inexperienced developers will use the full power of AOP in very evil ways. They might learn & fix this eventually ... or not.
AOP makes me think about Monkey Patching in Ruby ... except that in the Ruby community, there's a clear consensus that this is both great & dangerous. I might be wrong but I fear that the understanding of AOP in the .Net community is not yet there.
Gael Fraiteur
2010/12/13 9:47:08
Hi Steve,
Thank you for your comment.
I think everybody does IoC even without knowledge of it. There's no way to program without it nowadays, unless you program some firmware/bios from green field.
I agree with the message-passing argument. The definition of AOP does not specify that a message-passing convention should be respected, but PostSharp is strongly based on this analogy, and its design enforces it. On the logical level, PostSharp works as a proxy-based framework, but uses method proxies instead of class proxies, and these proxies are hardcoded in MSIL.
I disagree that applying aspects inside classes is pure evil. It is actually what the class designer may want. What if, as the developer of a class, you want to dispatch a method on a background thread? You can hardly break a class invariant with an aspect without knowing/wanting it (yes, you can make Math.Sin return 2, but don't tell you haven't done it on purpose!).
AOP is not monkey patching, this is a disciplined approach to metaprogramming. AOP gives you a set of primitive transformations that you can compose. These primitives are safe to use, and there is a robust composition mechanism. So the freedom you get with AOP is very controlled. AOP is not meant as a patching technology, even if it has been used for this purpose.
chris
2010/12/13 18:05:55
I could also bring the Good, the Bad and the Ugly of compiletime AOP, but I have already brought my point the post before.
Still, I like the syntactic sugar postsharp brings, but see it fundamentally as a sign for design smell. It componensates smell in different libraries, that's nice, nothing bad about that, but shouldn't be.
I think you are just using the wrong language for your problems. Try F# or Clojure on .net. A old friend is also getting active, Nemerle, which supports macros.
Pingbacks and trackbacks (4)
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
计算机用语中常见英文缩写和词组
Enterprise Java Community: JBoss Seam: A Deep Integration Framework
Java技术版图俯瞰——awesome
Python开源框架、类库、软件集合
Introducing RTSC - RTSC-Pedia
The Flex Programming Model
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服