What are the different methods for injecting cross-cutting concerns into a class so that I can minimize the coupling of the classes involved while keeping the code testable (TDD or otherwise)?
For example, consider if I have a class that requires both logging functionality and centralized exception management. Should I use DIP and inject both required concerns via an interface into the class that requires them? Should I use a service locater that I pass to each class that will require some cross cutting functionality? Is there a different solution altogether? Am I asking the wrong question entirely?
The Decorator design pattern is an excellent starting point for implementing cross-cutting concerns.
First you need to define an interface that models the service in question. Then you implement the real functionality of that service without thinking about your cross-cutting concern at all.
Then you can subsequently implement decorating classes that wrap around other instances and implement the desired cross-cutting concern.
This approach can be implemented entirely with Plain Old C# Objects (POCOs) and requires no extra frameworks.
However, if you get tired of writing all the extra decorators, you may want to use a framework. I have no experience with explicit AOP frameworks, but most DI Containers such as Castle Windsor offer AOP-like features.
Here's an example of using Decorators. Let's say that you have the following interface:
public interface IMyInterface
{
void DoStuff(string s);
}
Your concrete implementation may do something very interesting, such as writing the string to the Console:
public class ConsoleThing : IMyInterface
{
public void DoStuff(string s)
{
Console.WriteLine(s);
}
}
If you wish to log the DoStuff operation, you can now implement a logging Decorator:
public class LoggingThing : IMyInterface
{
private readonly IMyInterface innerThing;
public LoggingThing(IMyInterface innerThing)
{
this.innerThing = innerThing;
}
public void DoStuff(string s)
{
this.innerThing.DoStuff(s);
Log.Write("DoStuff", s);
}
}
You can keep writing new Decorators, like a caching Decorator or one that implements security and so on, and just wrap them around each other.
Note: I rarely recommend static interfaces, so the Log.Write interface is not a recommendation, but merely mean as a placeholder. In a real implemetation, I'd inject some kind of ILogger interface into LoggingThing.