A farewell to names

This past Saturday I woke up with a surprise. For some reason the answer to a problem that had lingered subconsciously for years inexplicably bubbled up fully formed into my head.

The name blame game

The problem was naming: specifically, named binding injections in Strange. Named bindings is a solution to an important problem, solved by our friends at Robotlegs. They knew that an application might need more than one implementation of an abstract class or interface, so when injecting they had to have a way to determine which concrete version to instantiate. To solve this, they used naming to differentiate one implementation from another. In Strange, it looks like this:

injectionBinder.Bind<IEnemyAI>().To<CrazyIvanAI>().ToName("Ivan");

This solved the problem, but in my view solved it rather inelegantly, because the client class that uses a named injection now needs to look like this:

[Inject("Ivan")]
public IEnemyAI aiModel { get; set; }

This is precisely the opposite of how dependency inversion is meant to work. The client is telling its  provider what it needs, instead of the other way around. That said, I had no better idea how to fix this, so it’s what we ended up using in Strange. That all changed with my flash of insight on Saturday.

Could naming, like the rest of binding, be inverted?

Not naming any names

So my mission was to supply injections to identical interfaces and abstract types without editing the consuming class. Which means what used to look like this:

public class EnemyMediatorOne
{
    [Inject("Ivan")]
    public IEnemyAI aiModel { get; set; }
}
public class EnemyMediatorTwo
{
    [Inject("George")]
    public IEnemyAI aiModel { get; set; }
}

Now needs to look like this:

public class EnemyMediatorOne
{
    [Inject]
    public IEnemyAI aiModel { get; set; }
}
public class EnemyMediatorTwo
{
    [Inject]
    public IEnemyAI aiModel { get; set; }
}

That latter example yields scarily similar injections, but there is one thing that obviously sets them apart: the classes in which they are defined. So we can use the client class as the identifier to separate one injection from another!

This leads us to a new InjectionBinding method: SupplyTo.

SupplyTo is an optional instruction in your injection binding that explicitly identifies a client class you want to have consume this injection.

Here’s an example:
(Note: there’s a bug — in WordPress I presume — that causes this example to render a bit oddly in some browsers. I got tired of trying to solve their issue. My apologies. I think the idea comes across. If you really need to see it correctly, it renders fine in Firefox.)

injectionBinder.Bind<IEnemyAI>()
    .To<BasicEnemy>()
injectionBinder.Bind<IEnemyAI>() 
    .To<BossEnemy>()
    .ToName("Boss") 
    .SupplyTo<RedBossMediator>() 
    .SupplyTo<BlueBossMediator>() 
    .SupplyTo<GreenBossMediator>();
injectionBinder.Bind<IEnemyAI>() 
    .To<BigBossEnemy>()
    .ToName("BigBoss")
    .SupplyTo<BigBossMediator>();

In the above example, we have three implementations of IEnemyAI:

  • BasicEnemy
  • BossEnemy
  • BigBossEnemy

By default, anyone who requests IEnemyAI receives an instance of BasicEnemy. But certain marked classes, such as RedBossMediator, receive a different class instance. And one class, BigBossMediator, receives its own special AI. The key point is that each mediator simply marks the interface for injection…no naming in the class is required.

public class StandardEnemyMediator
{
    [Inject]
    public IEnemyAI aiModel { get; set; }
}
public class RedBossMediator
{
    [Inject]
    public IEnemyAI aiModel { get; set; }
}
public class BigBossMediator
{
    [Inject]
    public IEnemyAI aiModel { get; set; }
}

The result is a much better form of inversion, and more flexible code (which is what we’re always after, right?

A couple things to call out from our example. First, note that we still name the binding itself, whenever there’s more than one key. This is important both for backwards compatibility and in order to retrieve the binding, should you ever need to. And see how we apply multiple SupplyTo’s in a chain. This makes it convenient to list any number of consumers for this injection.

Finally, I’ll mention that this change should be fully backwards compatible. So you don’t need to use it if you don’t want to. And you should be able to safely mix-and-match the new approach with the old one.

Naming the limitation

There is one important caveat to this model. Since we’re using the client class as the key, there’s no way to use this technique to inject two of the same interface/abstraction into the same client class. That’s a corner case, albeit an important one, in which you’ll still have to rely on the old-fashioned naming method.

The name of the branch

This work is now on the branch invert-names. If all checks out, we hope to release it as part of the next major release of the framework. Happy binding!

Advertisements

2 thoughts on “A farewell to names

  1. Reading this made me feel that I’ve been implementing factories really wrong, as this new nameless convention will not allow me to do the following:

    public class Factory
    {
    [Inject(Products.One)]
    public Product ProductOne { get; set; }

    public Product GetProduct(Products name)
    {
    switch (name)
    {
    case Products.One:
    return ProductOne;
    default:
    return null;
    }
    }
    }

    • Well, I don’t normally use factories in this fashion (or at all, really). The Injector acts as a factory (i.e., it’ll create instances on-the-fly) so I rarely need anything quite like this. When I do, e.g., when I need to configure each instance as I instantiate, I use a Command to do the configuration. Thus naming my instances is only rarely necessary. Does that shed any light for you?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s