This article describes how object roles can be indicated by metadata.

In my overview article on Role Hints I described how making object roles explicit can help making code more object-oriented. One way code can convey information about the role played by an object is by leveraging metadata. In .NET that would often take the form of attributes, but you can also maintain the metadata in a separate data structure, such as a dictionary.

Metadata can provide useful Role Hints when there are many potential objects to choose from.

Example: Selecting a shipping Strategy #

Consider a web shop. When you take your shopping basket to checkout you are presented with a choice of shipping methods, e.g. Standard, Express, and Price Saver. Since this is a web application, the user's choice must be communicated to the application code as some sort of primitive type. In this example, assume an enum:

public enum ShippingMethod
{
    Standard = 0,
    Express,
    PriceSaver
}

Calculating the shipping cost for a basket may be a complex operation that involves the total weight and size of the basket, as well as the distance it has to travel. If the web shop has geographically distributed warehouses, it may be cheaper to ship from a warehouse closer to the customer. However, if the closest warehouse doesn't have all items in stock, there may be a better way to optimize profit. Again, that calculation likely depends on the shipping method chosen by the customer. Thus, a common solution is to select a shipping cost calculation Strategy based on the user's selection. A simplified example may look like this:

public class BasketCostCalculator
{
    private readonly IShippingCostCalculatorFactory shippingFactory;
 
    public BasketCostCalculator(
        IShippingCostCalculatorFactory shippingCostCalculatorFactory)
    {
        this.shippingFactory = shippingCostCalculatorFactory;
    }
 
    public int CalculatePrice(
        ShoppingBasket basket,
        ShippingMethod shippingMethod)
    {
        var shippingCalculator =
            this.shippingFactory.GetCalculator(shippingMethod);
 
        return shippingCalculator.CalculatePrice(basket) + basket.Total;
    }
}

A naïve attempt at an implementation of the IShippingCostCalculatorFactory may involve a switch statement:

public IBasketCalculator GetCalculator(ShippingMethod shippingMethod)
{
    switch (shippingMethod)
    {
        case ShippingMethod.Express:
            return new ExpressShippingCostCalculator();
        case ShippingMethod.PriceSaver:
            return new PriceSaverShippingCostCalculator();
        case ShippingMethod.Standard:
        default:
            return new StandardShippingCostCalculator();
    }
}

Now, before you pull Refactoring at me and tell me to replace the enum with a polymorphic type, I must remind you that at the boundaries, applications aren't object-oriented. At some place, close to the application boundary, the application must translate an incoming primitive to a polymorphic type. That's the responsibility of something like the ShippingCostCalculatorFactory.

There are several problems with the above implementation of IShippingCostCalculatorFactory. In the simplified example code, all three implementations of IBasketCalculator have default constructors, but that's not likely to be the case. Recall that calculating shipping cost involves complicated business rules. Not only are those classes likely to need all sorts of configuration data to determine price per weight range, etc. but they might even need to perform lookups against external system - such as getting a qoute from an external carrier. In other words, the ExpressShippingCostCalculator, PriceSaverShippingCostCalculator, and StandardShippingCostCalculator are unlikely to have default constructors.

There are various ways to implement such an Abstract Factory, but none of them may fit perfectly. Another option is to associate metadata with each implementation. Using attributes for such purpose is the classic .NET solution:

public class HandlesShippingMethodAttribute : Attribute
{
    private readonly ShippingMethod shippingMethod;
 
    public HandlesShippingMethodAttribute(ShippingMethod shippingMethod)
    {
        this.shippingMethod = shippingMethod;
    }
 
    public ShippingMethod ShippingMethod
    {
        get { return this.shippingMethod; }
    }
}

You can now adorn each IBasketCalculator implementation with this attribute:

[HandlesShippingMethod(ShippingMethod.Express)]
public class ExpressShippingCostCalculator : IBasketCalculator
{
    public int CalculatePrice(ShoppingBasket basket)
    {
        /* Perform some complicated price calculation based on the
         * basket argument here. */
        return 1337;
    }
}

Obviously, the other IBasketCalculator implementations get a similar attribute, only with a different ShippingMethod value. This effectively provides a hint about the role of each IBasketCalculator implementation. Not only is the ExpressShippingCostCalculator a basket calculator; it specifically handles the Express shipping method.

You can now implement IShippingCostCalculatorFactory using these Role Hints:

public class ShippingCostCalculatorFactory : IShippingCostCalculatorFactory
{
    private readonly IEnumerable<IBasketCalculator> candidates;
 
    public ShippingCostCalculatorFactory(
        IEnumerable<IBasketCalculator> candidates)
    {
        this.candidates = candidates;
    }
 
    public IBasketCalculator GetCalculator(ShippingMethod shippingMethod)
    {
        return (from c in this.candidates
                let handledMethod = c
                    .GetType()
                    .GetCustomAttributes<HandlesShippingMethodAttribute>()
                    .SingleOrDefault()
                where handledMethod != null
                && handledMethod.ShippingMethod == shippingMethod
                select c).Single();
    }
}

This implementation is created with a sequence of IBasketCalculator candidates and then selects the matching candidate upon each GetCalculator method call. (Notice that I decided that candidates was a better Role Hint than e.g. calculators.) To find a match, the method looks through the candidates and examines each candidate's [HandlesShippingMethod] attribute.

You may think that this is horribly inefficient because it virtually guarantees that the majority of the injected candidates are never going to be used in this method call, but that's not a concern.

Example: detached metadata #

The use of attributes has been a long-standing design principle in .NET, but seems to me to offer a poor combination of tight coupling and Primitive Obsession. To be fair, it looks like even in the BCL, the more modern APIs are less based on attributes, and more based on other alternatives. In other posts in this series on Role Hints I'll describe other ways to match objects, but even when working with metadata there are other alternatives.

One alternative is to decouple the metadata from the type itself. There's no particular reason the metadata should be compiled into each type (and sometimes, if you don't own the type in question, you may not be able to do this). Instead, you can define the metadata in a simple map. Remember, 'metadata' simply means 'data about data'.

public class ShippingCostCalculatorFactory : IShippingCostCalculatorFactory
{
    private readonly IDictionary<ShippingMethod, IBasketCalculator> map;
 
    public ShippingCostCalculatorFactory(
        IDictionary<ShippingMethod, IBasketCalculator> map)
    {
        this.map = map;
    }
 
    public IBasketCalculator GetCalculator(ShippingMethod shippingMethod)
    {
        return this.map[shippingMethod];
    }
}

That's a much simpler implementation than before, and only requires that you supply the appropriately populated dictionary in the application's Composition Root.

Summary #

Metadata, such as attributes, can be used to indicate the role played by an object. This is particularly useful when you have to select among several candidates that all have the same type. However, while the Design Guidelines for Developing Class Libraries still seems to favor the use of attributes for metadata, this isn't the most modern approach. One of the problems is that it is likely to tightly couple the resulting code. In the above example, the ShippingMethod enum is a pure boundary concern. It may be defined in the UI layer (or module) of the application, while you might prefer the shipping cost calculators to be implemented in the Domain Model (since they contain complicated business logic). However, using the ShippingMethod enum in an attribute and placing that attribute on each implementation couples the Domain Model to the user interface.

One remedy for the problem of tight coupling is to express the selected shipping method as a more general type, such as a string or integer. Another is to use detached metadata, as in the second example.


Comments

Phil Sandler #
I know this post is about roles, but I think your example illustrates a question that I ask (and get asked) constantly about implementing factories using DI. I have gone around and around this issue and tried various approaches, including the map solution you provided. The attribute idea is new to me, and I will give it some consideration.

Bottom line: I've landed on just using the container directly inside of these factories. I am completely on board with the idea of a single call to Resolve() on the container, but this is one case where I have decided it's "easier" to consciously violate that principle. I feel like it's a bit more explicit--developers at least know to look in the registration code to figure out which implementation will be returned. Using a map just creates one more layer that essentially accomplishes the same thing.

A switch statement is even better and more explicit (as an aside, I don't know why the switch statement gets such a bad rap). But as you noted, a switch statement won't work if the calculator itself has dependencies that need resolution. In the past, I have actually injected the needed dependencies into the factory, and supplied them to the implementations via new().

Anyway, I'm enjoying this series and getting some nice insights from it.
2013-01-09 15:33 UTC
James Nail #
On the topic of doing this via IoC, Autofac deals with metadata quite well. I've typically used things like named/keyed registrations for specific strategy implementations, but I can see where a developer dropped into the middle of the codebase could be confused amid the indirection.
So an attribute on the implementation class can serve two important purposes here -- first, it does indeed make the intended role of the class explicit, and second, it can give your IoC container registration hints, particularly helpful when using assembly scanning / convention-based registrations.
2013-01-10 16:25 UTC
Andrey K #
Mark, big thanks for your "january post"!

The detached metadata "injecting mapping dictionary from composition root" sample is great!

In my practice I did such mapping-thing with inharitance and the result is a lot of factories all-over the code, that knows about DI-container (because of lazy-initialization with many dependencies inside each factory)

With some modifications, like generic-parameters and lazy-initialization, injection dictionary over constructor inside such universal one-universal-factory-class really could be great solution for the most cases.
2013-01-10 19:59 UTC


Wish to comment?

You can add a comment to this post by sending me a pull request. Alternatively, you can discuss this post on Twitter or somewhere else with a permalink. Ping me with the link, and I may respond.

Published

Wednesday, 09 January 2013 10:42:20 UTC

Tags



"Our team wholeheartedly endorses Mark. His expert service provides tremendous value."
Hire me!
Published: Wednesday, 09 January 2013 10:42:20 UTC