Sep 9 2012

The Decorator Pattern, Done Right, With StructureMap

Category: IoC | StructureMap | DesignPatternsMatt @ 08:16

One of the cool bits of black magic I showed during my “StructureMapping Your Way to Better Software” talk was an extension method for registering decorators using StructureMap.  In this post, I’ll show you how it works.

 

image

EnrichWith Says What?

StructureMap actually supports the decorator pattern right out of the box.  This functionality is cleverly concealed in plain sight in the EnrichWith method:

var container = new Container(cfg =>
    {
        //...
        
        cfg.For<IProductRepository>()
            .Use<InMemoryProductRepository>()
            .EnrichWith((ctx, r) =>
                {
                    return new ProductRepoLogger(r, ctx.GetInstance<User>());
                });

        //...
    });

var repo = container.GetInstance<IProductRepository>();
//Repo is actually a ProductRepoLogger that wraps an InMemoryProductRepository.

There are several problems with this method though.  First, it doesn’t read very well.  At all.  Especially when you want to apply multiple decorators:

var container = new Container(cfg =>
    {
        //...
        
        cfg.For<IProductRepository>()
            .Use<InMemoryProductRepository>()
            .EnrichWith((ctx, r) =>
                {
                    return new ProductRepoLogger(
                        new ProductSecurityDecorator(r, ctx.GetInstance<IProductAuthorizer>(), ctx.GetInstance<User>()), 
                        ctx.GetInstance<User>());
                });

        //...
    });

var repo = container.GetInstance<IProductRepository>();
//Repo is now ProductRepoLogger that wraps a ProductSecurityDecorator that wraps an InMemoryProductRepository.

Imagine if you added a third (or fourth) decorator, or if your decorators had more dependencies than the simple example above.

The other, bigger problem is that it tightly-couples your configuration code to implementation details of your decorators.  What happens if you want need to add an additional dependency to your decorator?  You’ll need to go back and update anywhere that you’ve applied that decorator.  That really defeats some of the advantages of using an IoC container in the first place. 

A Better Way

If the above code doesn’t strike you as something you want to have to look at ever again, I made an extension method for you:

var container = new Container(cfg =>
    {
        //...
        
        cfg.For<IProductRepository>().Use<InMemoryProductRepository>()
            .Decorate().With<ProductSecurityDecorator>()
            .AndThen<ProductRepoLogger>()
            .AndThen<RudeProductRepoLogger>();
        cfg.For<IProductAuthorizer>().Use<ProductAuthorizer>();
        
        //...
    });

var repo = container.GetInstance<IProductRepository>();

The above allows you to chain multiple decorators together cleanly.  No more nesting up ‘new’ statements, no more using the context to manually locate dependencies.  The decorators are created through the container, so they can take full advantage of dependency injection.  That means no more tight coupling between your config code and your decorator constructors!

How Does it Work?

I’m a bit embarrassed to say that it actually took me three attempts to implement this alternative.  I tried and failed twice before because I overlooked a simple, obvious approach: register the “inner” instance using IContext.RegisterDefault!   The extension method returns a DecoratedInstance that can be used to recursively build up decorator definitions:

public class DecoratedInstance<TTarget>
{
    //...
    
    public DecoratedInstance<TTarget> AndThen<TDecorator>()
    {
        //Must be captured as a local variable, otherwise the closure's decorator will
        //always be the outer-most decorator, causing a Stack Overflow.
        var previousDecorator = _decorator;

        ContextEnrichmentHandler<TTarget> newDecorator = (ctx, t) =>
            {
                var pluginType = ctx.BuildStack.Current.RequestedType;

                var innerInstance = previousDecorator(ctx, t);

                ctx.RegisterDefault(pluginType, innerInstance);

                return ctx.GetInstance<TDecorator>();
            };

        _instance.EnrichWith(newDecorator);

        _decorator = newDecorator;

        return this;
    }
}

What’s It Good For?

The decorator pattern is one of my favorite design patterns.  It’s an awesome way to encapsulate behaviors and responsibilities into composable pieces.  Here are a few examples using a a simple repository:

Logging

public class ProductRepoLogger : IProductRepository
{
    private readonly IProductRepository _target;
    private readonly User _user;

    public ProductRepoLogger(IProductRepository target, User user)
    {
        _target = target;
        _user = user;
    }

    public Product Find(int id)
    {
        Console.WriteLine("{0} is requesting product {1}.", _user, id);

        return _target.Find(id);
    }
}

Security

public class ProductSecurityDecorator : IProductRepository
{
    private readonly IProductRepository _target;
    private readonly IProductAuthorizer _securityService;
    private readonly User _currentUser;

    public ProductSecurityDecorator(IProductRepository target, IProductAuthorizer securityService, User currentUser)
    {
        _target = target;
        _securityService = securityService;
        _currentUser = currentUser;
    }

    public Product Find(int id)
    {
        if (_securityService.IsUserAuthorizedToAccessProduct(_currentUser, id))
        {
            return _target.Find(id);
        }
        else
        {
            throw new SecurityException("You are not authorized to access this product!");
        } 
    }
}

Caching

public class ProductCachingDecorator : IProductRepository
{
    private readonly IProductRepository _innerRepo;
    private readonly ConcurrentDictionary<int, Product> _cache;

    public ProductCachingDecorator(IProductRepository innerRepo)
    {
        _innerRepo = innerRepo;
        _cache = new ConcurrentDictionary<int, Product>();
    }

    public Product Find(int id)
    {
        return _cache.GetOrAdd(id, _ => _innerRepo.Find(id));
    }
}

By breaking behaviors apart, the original repository remains simple and clean, and the behaviors can be reused with any repository that implements the interface. 

Where Can I Get It?

I don’t have a good project for this (yet), so I’ve created a Gist with the required pieces.  If anyone has any suggestions on where to put this functionality, let me know.  If you want to see a full example, check out my “Learning Structure” map sample project.  Questions or comments?  Leave ‘em below!

Tags: , ,

blog comments powered by Disqus