Comments (5)
Steps:
- Given a class that implements some business logic
public class BizHandler : IHandler<int, int> {}
- Write an interceptor, which implements
IInterceptor<THandler, TIn, TOut>
public class Debugger<THandler, TIn, TOut> : IInterceptor<THandler, TIn, TOut>
where THandler : IHandler<TIn, TOut>
{
public TOut Intercept(IHandler<TIn, TOut> handler, TIn input, Func<TIn, TOut> next)
{
// intercept handler's execution here
}
}
- Register above interceptor with the IoC container as collection.
- Add the handler to a composite handler in the constructor
public class Composite : Handler<int, int>
{
public Composite(IServiceProvider serviceProvider)
base(serviceProvider)
{
AddHandler<BizHandler>();
}
}
Under the hood, the DefaultInterceptionStrategy
will
- resolve all interceptors which are resolvable as
IInterceptor<BizHandler, int, int>
- decorate the
BizHandler
with the interceptors - then add the decorated
BizHandler
to the end of the chain
If you want to roll your own interception strategy
- write a class that implements
IInterceptionStrategy
- pass a strategy object to the
AddHandler
method, e.g.AddHandler<BizHandler>(myInterceptionStrategy)
from responsibilitychain.
Reopen due to feature #5
from responsibilitychain.
Doing interception work within the AddHandler method (and therefore constructor) is rarely a good idea.
So interception-related code will be removed from the code base.
References:
https://blog.ploeh.dk/2011/03/03/InjectionConstructorsshouldbesimple/
https://vuscode.wordpress.com/2009/10/16/inversion-of-control-single-responsibility-principle-and-nikola-s-laws-of-dependency-injection/
http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/
from responsibilitychain.
As of the latest release which implements above comment, all nested handlers are now being exposed in the constructor as the dependencies, and are resolved by IoC container or just manually newing up them.
Example
// A parser, which basically converts something like "1h 30m" to 90 minutes
IHandler<string, int> parser = new WorkLogParser(
new WorkLogValidator(
new WorkLogMustNotBeNullOrEmptyRule(),
new ThereShouldBeNoUnitDuplicationRule(),
new UnitsMustBeInDescendingOrderRule()
),
new IndividualUnitParser(
new WeekParser(),
new DayParser(),
new HourParser(),
new MinuteParser()
)
);
We've seen another problem with this approach though. Most of the time, the Handle
method of a nested handler, e.g. WeekParser
, is not virtual, i.e. not overrideable, so that it is not possible for the IoC container to create a callback hook (think about aspect-oriented programming).
To overcome this, there were two approaches being proposed:
- Inject a list of
IHandler<TIn, TOut>
instead of individual concrete handlers. There are two issues with this approach:
1.1. The number and the order of dependencies are not obvious to the composite handler. You register a list of nested handlers to a composite in a separate class (Autofac module, Windsor installer, composition root, or whatever). It makes it hard to follow, thus hard to maintain. The number and the order of dependencies are the business logic of the composite handler, thus it should be visible within the composite itself, not elsewhere.
1.2. As a consequence, it might lead to a situation, in which a unit test passed, yet it causes exception in production due to different object graph's setup. - The second approach is to explicitly declare all handlers (or just relevant ones) virtual, and intercept these handlers by creating dynamic proxies on the fly. This would be a repetitive and error-prone task.
We couldn't find a clean way to inject an intercepted handler into the constructor. To realize callback hooks, it is necessary for the IoC container to have a chance to intercept the resolved THandler
within the AddHandler
method. This interception process should be
- invisible as if interception feature wouldn't exist. It should not require framework's user to have additional configuration on their side.
- not tightly-coupled to any interception mechanism
One main disadvantage with this approach is that it depends on a global singleton instance, such as InterceptionStrategy.Current
. It makes it impossible to run unit tests in parallel if there are some tests which change the global strategy.
There is another advantage with this approach though.
In C#, all methods are non-virtual by default. On the contrary, all methods in Java are virtual by default.
You can imagine a composite handler with its nested handlers exposed via composite's constructor as an API, which is non-virtual by default. This is the C# way.
And then there are interceptors, which suddenly make all (or just several) nested handlers "virtual" without touching the handlers' source code. Isn't it a nice problem to have?
from responsibilitychain.
Resolved with commit 4a1e6db.
from responsibilitychain.
Related Issues (13)
- Have a `Handle` method, which accepts `input` object only and no `next` delegate
- Should be able to cancel an asynchronous operation HOT 1
- Consolidate projects' settings via Directory.Build.props and Directory.Build.targets
- Add CancellationToken as optional parameter of AsyncHandler.HandleAsync(TIn input)
- The framework's default interception strategy should be configurable HOT 1
- Override InterceptedHandler.ToString method to have a better view into InterceptedHandler to facilitate debugging HOT 1
- IInterceptionStrategy should be injected at constructor level of Handler for simplicity and consistency HOT 2
- All nested handlers and their dependencies should be injected via constructor HOT 2
- Should cache ChainedDelegate within Handler composite to improve performance on the second call to Handle()
- Allow a composite handler to have no child
- Have support for asynchronous handlers
- Asynchronous handler added to a composite should be intercepted
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from responsibilitychain.