How to deal with cross-cutting concerns in a OO Application? Use Singleton? Dependency Injection? What?
Let's say that I'm currently designing an application where I will need to use a global timing system (it's a cross-cutting concern). I'll need to access data from that global timing system from basically anywhere in my app and it's not like I can see that "this part of the appli开发者_开发问答cation will need it while the other won't".
My question is.. should I design that as a kind Ambient Context (in this case, Singleton), or should I try to devise other way to accomodate for this?
I certainly don't feel it's correct to make all my classes have to get this global timing class passed to them by constructor injection. Lot's of time I would have to pass the reference really down the chain until some class eventually needs it. On the other hand, it would turn everything more clear from the reading stand point(it makes it clear what my classes' dependencies are).
How do people generally deal with this? Are there any other techniques around that can help with this? AOP maybe?
PS: The global timing system was just an idea I took from a book I'm currently reading. A logging system would be another good example of this kind of problem.
Thanks
As you suggest, Aspect Oriented Programming (AOP) is designed with just this sort of thing in mind, so I'd certainly check that out.
While constructor injection does seem a hassle in this scenario, using an IOC container might help reduce your pain a bit. A singleton does seem to fit the bill well, but they bring issues with testability due to the staticness - but you can get round this by splitting it into two.
If everybody needs it and it's a one of a kind (just like the log you mentioned), it's definitely a singleton.
A static singleton class is the way that I see this dealt with most of the time. Using logging as a more commonly seen cross-cutting concern, I always see my logging calls looking like this:
public class SomeClassThatDoesStuff
{
private static readonly ILog log =
LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
public void DoStuff()
{
log.Info("Doing something");
}
}
You're global time class would seem like a similar type of cross-cutting concern and I'd approach it the same way
I agree that doing it with DI is going to be tedious and not terribly pragmatic.
What I did with aop:
public abstract aspect AutoInjectAspect<T> {
/**
* Implemented by the concrete class to decide what value to return.
* @param joinPoint
* @return
*/
abstract public T getInjectedValue(JoinPoint joinPoint);
protected pointcut autoInjectField() : get(@AutoInject T *);
/**
* override the "get" of the otherwise null value with the
* value to be injected
* @return the injected field
*/
T around(): autoInjectField() {
return getInjectedValue(thisJoinPoint);
}
}
And then made a concrete aspect:
public aspect SLF4jLoggerInjector extends AutoInjectAspect<Logger> {
@Override
public Logger getInjectedValue(JoinPoint joinPoint) {
Class classInjected = joinPoint.getStaticPart()
.getSignature().getDeclaringType();
Logger logger = LoggerFactory.getLogger(classInjected);
return logger;
}
}
Then make a annotation "@AutoInject" and put that on the field you want to inject into.
That being said, you could also the IOC container route.
Regardless of whether you implement it as a singleton I still recommend you pass it around by dependency injection. I also recommend you don't implement something as singleton yourself and defer to a DI framework giving you a singleton context that any first class IOC library will do.
I think you are right, that some types are just not appropiate for constructor injection; and some times you need to keep track of one context of some sort in a broader way than within a set of objects.
When I face this problem, I will often create a Singleton, but have the initialization logic call the dependency injection framework to do the actual instantiation. This leads to coupling on a specific static method (and some will oppose to this) - but at the same time I am free to change the implementation whenever I feel like it (and perhaps for some types have a different implementation in the test project, or something). And I get the benefit of not having to pass around a very common type between all the objects constructors.
Of course, you should only do this, when the type really is needed in a lot of places. Otherwise, use normal constructor injection. Exactly when "it's okay" is up to you to decide.
精彩评论