views:

89

answers:

3

I am writing a small utility for myself so whilst I am using Unity currently, I can change to a different IoC container if it will allow me to get around this problem.

I want to create a class that is given some data when it is created, but is immutable after that, normally I would do this with:

class SomeItem
{
    public SomeItem(string name)
    {
        Name = name;
        Hash = new SomeHashingClass().GenerateHash(name);
    }

    public string Name { get; private set; }

    public string Hash { get; private set; }
}

The problem is the dependency on SomeHashingClass in the constructor, how do I inject it whilst keeping the object immutable?

One way would be to have the constructor take just the dependency, then have a method which is the same as the current constructor to do the real work. However I hate that idea as it could leave the object in a state of existing but being totally invalid. And code would need to be written to make sure the method can only be called once.

The other way I can see to do it is to create a 'SomeItemFactory' class that unity resolves, then manually doing the dependency injection for SomeItem, however this doubles the amount of code:

class SomeItemFactory
{
    IHashingClass _hashingClass;

    public SomeItemFactory(IHashingClass hashingClass)
    {
        _hashingClass = hashingClass;
    }

    public SomeItem Create(string name)
    {
        return new SomeItem(_hashingClass, name);
    }
}

class SomeItem
{
    public SomeItem(IHashingClass hashingClass, string name)
    {
        Name = name;
        Hash = hashingClass.GenerateHash(name);
    }

    public string Name { get; private set; }

    public string Hash { get; private set; }
}

Please tell me there is a clean way to do this. Why is there no method like:

unityContainer.Resolve<SomeItem>("the items name");
A: 

It seems you can pass parameters to unity's Resolve() method. See the accepted answer of this question for details: http://stackoverflow.com/questions/787001/can-i-pass-constructor-parameters-to-unitys-resolve-method

M4N
A: 

It is possible to do as you say in unity

unityContainer.Resolve<ISomeInterface>("MyMappingName");

In config, assuming you are using a config file, you can have

<typeAliases>
    <typeAlias alias="SomeInterface" type="ISomeInterface, SomeAssembly" />
    <typeAlias alias="SomeConcreteType" type="SomeType, SomeAssembly" />
</typeAliases>

<containers>
    <container>
        <type type="SomeInterface" mapTo="SomeConcreteType" name="MyMappingName" />
    </container>
</containers>
simon_bellis
+3  A: 

While you can, indeed, pass parameters to the Resolve method, consider carefully if this is actually the correct design.

Why do you want to do this in the first place? Is is because you want to use Unity as a Service Locator? That's really the only reason I can think of, but I consider this an anti-pattern.

Use of DI Containers should follow the Hollywood Principle: tell it to resolve the entire application graph at the Composition Root and then forget all about it.

In your particular case, you can either keep SomeItem as is, but if you want to be able to vary the HashingClass as a dependency, you need to inject it into SomeItem, and Constructor Injection is the best option.

If you only need a single SomeItem instance in your application, you can wire it up like that in Unity, but if you need to create several instances, an Abstract Factory is the correct approach.

Your examples are almost there: you just need to extract an interface from SomeItemFactory and take a dependency on this ISomeItemFactory in any consumer that needs to create SomeItem instances.

It may look like more code, but lines of code are not a particularly good metric of code quality in any case (one way or the other). However, this approach allows you to follow the Single Responsibility Principle and vary creation and hashing of SomeItem independently of each other.

Notice that none of these principles are directed specifically at Unity, but applies broadly to DI in general.

Mark Seemann