25

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?

Stacy Vicknair
  • 1,631
  • 1
  • 13
  • 12

2 Answers2

26

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.

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • I've considered decorator as well. I suppose if I keep my methods concise enough then I won't be sacrificing the granularity of control I might expect from the example solutions I proposed. Is there any reason you would prefer Decorator? Because the decorated class needs no knowledge of the cross-cutting concerns? – Stacy Vicknair Nov 10 '09 at 16:12
  • Is this your preferred solution to cross-cutting concerns when you handle them? – Stacy Vicknair Nov 10 '09 at 16:16
  • I prefer Decorator over injection of Strategies because it keeps the real implementation squeky clean. I often prefer that, but may occasionally also use Interception as provided by e.g. Castle Windsor. – Mark Seemann Nov 10 '09 at 16:18
  • 1
    Instead of this.innerThing(s), should it be this.innerThing.DoStuff(s)? – Andrew May 19 '10 at 05:32
  • @Mark, so do you create a bunch of separate `Logging...` decorator classes for `IMyOrderCreator`, `IMyOrderShipper`, `IMyCustomerCreator`, etc.? I like where @Steven's taken this with http://stackoverflow.com/a/9915056/326110 and http://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=92 but I can't bring myself to go down the path of defining everything that I might want to log as a command or query. – mo. Aug 13 '13 at 17:21
  • I often just create the concrete Decorators as classes, but dynamic interception is also an option if you have many of those: http://blog.ploeh.dk/2010/09/20/InstrumentationwithDecoratorsandInterceptors See also http://channel9.msdn.com/Events/GOTO/GOTO-2011-Copenhagen/CPH-Aspect-Oriented-Programming-with-Dependency-Injection – Mark Seemann Aug 13 '13 at 19:45
  • 1
    Could you elaborate on what happens if I need to log something that only the original `DoStuff` implementation knows about? For example, imagine that it does a few things, and in the middle of the method I would like to log the value of a local variable. The decorator in this case does not have access to that, but putting the logger on the raw implementation would violate SRP again. How would you handle that? – julealgon Aug 24 '15 at 16:29
  • @julealgon If that need arises, my first reaction would be to investigate if not the method in question [does too much](https://en.wikipedia.org/wiki/Single_responsibility_principle). In the 10-15 years I've been writing SOLID code, the need for logging internal has never arisen for me. If it does because of other people's code, I refactor by breaking the offending method into smaller methods. – Mark Seemann Aug 24 '15 at 19:02
  • I think proxy pattern is much more fitted than the decorator. – Roath So Mar 22 '17 at 20:09
  • Wow, always learning thanks! Great to know even SimpleInjector can swap out any class and replace with a new decorated class – Ian May 22 '17 at 13:56
  • @MarkSeemann, and how would you a DI and `Console` `Logging` thing? When with decorator pattern it's up to the developer to 'chain' them. In your ctor you can't `ctor(LoggingThing thing)` expecting that it would also be a `Console` thing. Which means someone somewhere will have to create an instance of it. There would be only one sensible thing to do in this case - during the IoC container setup establish that `IMyInterface` is a `Consol` `Logging` thing. Which entirely defeats the purpose of the decorator. – hyankov Oct 29 '17 at 16:28
  • @HristoYankov I'm not sure I understand your comment, but if you have a question, you're welcome to ask a new question here on Stack Overflow, and ping me with a link. If so, I'll be happy to take a look at it. – Mark Seemann Oct 29 '17 at 18:41
  • @MarkSeemann fair enough, I couldn't formulate my question well. But in short - the suggested approach won't work well with DI because in some cases you might need `IMyInterface` wrapped in a specific set of cross-cutting concerns, in another it might be a different set. e.g. in one case you may want Security with it, in another not. – hyankov Oct 29 '17 at 18:54
  • @HristoYankov I'm always intrigued when someone claims that something can't be done... How do you propose to justify that claim? – Mark Seemann Oct 29 '17 at 18:59
  • @MarkSeemann, I made a semantic error, sorry! What I really mean is, I don't see how that would work well with IoC container, not DI. Unless you setup the IoC container to instantiate different decorators chain, for different methods. – hyankov Oct 29 '17 at 19:36
4

You can use the Observer pattern.

The Subject holds a collection of Observers. When the Subject performs an action, it notifies the Observers of the change. The Observers can then perform an action without the Subject caring what that action is.

Here's an example:

public interface IThingObserver
{
    void Notify();  // can also pass in a parameter with event information
}

public class Thing
{
    private readonly ICollection<IThingObserver> observers;

    public Thing()
    {
        observers = new List<IThingObserver>();
    }

    public void RegisterObserver(IThingObserver observer)
    {
        observers.Add(observer);
    }

    public void UnregisterObserver(IThingObserver observer)
    {
        observers.Remove(observer);
    }

    private void NotifyObservers()
    {
        foreach (IThingObserver observer in observers)
        {
            observer.Notify();
        }
    }

    public void DoIt()
    {
        Console.WriteLine("Doing it...");
        NotifyObservers();
    }
}

public class LoggingThingObserver : IThingObserver
{
    public void Notify()
    {
        Log.Write("It is done.");
    }
}

I'm used to Java, so please forgive any syntax errors.

Eva
  • 4,397
  • 5
  • 43
  • 65
  • I know I originally made this in '09, but thanks for the answer! Observer is a good recommendation that really helps separate the concerns and hits well with OCP. – Stacy Vicknair Jan 07 '14 at 16:42