This article describes how object roles can be indicated by parts of a type name.

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 to let a part of a class' name convey that information. This is often very useful when using Convention over Configuration.

While a class can have an elaborate and concise name, a part of that name can communicate a particular role. If the name is complex enough, it can hint at multiple roles.

Example: Selecting a shipping Strategy #

As an example, consider the shipping Strategy selection problem from a previous post. Does attribute-based metadata like this really add any value?

[HandlesShippingMethod(ShippingMethod.Express)]
public class ExpressShippingCostCalculator : IBasketCalculator

Notice how the term Express appears twice on two lines of code. You could successfully argue that the DRY principle is being violated here. This becomes even more apparent when considering the detached metadata example. Here's a static way to populate the map (in F# just because I can):

let map = Dictionary<ShippingMethod, IBasketCalculator>()
map.Add(ShippingMethod.Standard,
                       StandardShippingCostCalculator())
map.Add(ShippingMethod.Express,
                       ExpressShippingCostCalculator())
map.Add(ShippingMethod.PriceSaver,
                       PriceSaverShippingCostCalculator())

This code snippet uses some slightly unorthodox (but still valid) formatting to highlight the problem. Instead of a single statement per shipping method, you could just as well write an algorithm that populates this map based on the first part of each calculator's name. Follow that train of thought to its logical conclusion, and you don't even need the map:

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 t = c.GetType()
                where t.Name.StartsWith(shippingMethod.ToString())
                select c).First();
    }
}

This implementation uses the start of the type name of each candidate as a Role Hint. The ExpressShippingCostCalculator already effectively indicates that it calculates shipping cost for the Express shipping method.

This is an example of Convention over Configuration. Follow a simple naming convention, and things just work. This isn't an irreversible decision. If, in the future, you discover that you need a more elaborate selection algorithm, you can always modify the ShippingCostCalculatorFactory class (or, if you wish to adhere to the Open/Closed Principle, add an alternative implementation of IShippingCostCalculatorFactory).

Example: ASP.NET MVC Controllers #

The default routing algorithm in ASP.NET MVC works this way. An incoming request to /basket/1234 is handled by a BasketController instance, /product/3457 by a ProductController instance, and so on.

The ASP.NET Web API works the same way, too.

Summary #

Using a part of a type name as a Role Hint is very common when using Convention over Configuration. Many developers react strongly against this approach because they feel that the loss of type safety and the use of Reflection makes this a bit 'too magical' for their tastes. However, even when using attributes, you can easily forget to add an attribute to a class, so in the end you must rely on testing to be sure that everything works. The type safety of attributes is often an illusion.

The great benefit of Convention over Configuration is that it significantly cuts down on the number of moving parts. It also 'forces' you (and your team) to write more consistent code, because the overall application is simply not going to work if you don't follow the conventions.


Comments

It's interesting how conventions can popup from places you wouldn't even expect them to appear.
I've used MVP pattern in Unity3D game development, where I had like MosterView and MonsterPresenter autowired by assembly scanning. As result I have IPresenter as input into View and IoC container that discover and inject correct Presenter implementation into View. I also wrote additional test, where I assert if every view has corresponding presenter, such that I would discover convention violation not at run-time, but at tests running step. Just reduces feedback time a little bit.
This idea came after watching your "Conventions: Make your code consistent" presentation. Thanks.
2013-01-23 15:42 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

Friday, 11 January 2013 11:07:55 UTC

Tags



"Our team wholeheartedly endorses Mark. His expert service provides tremendous value."
Hire me!
Published: Friday, 11 January 2013 11:07:55 UTC