Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is Func<in T, out TResult> appropriate to use as a ctor arg when applying Dependency Injection?

Example:

public class BusinessTransactionFactory<T>  where T : IBusinessTransaction
{
    readonly Func<Type, IBusinessTransaction> _createTransaction;

    public BusinessTransactionFactory(Func<Type, IBusinessTransaction> createTransaction)
    {
        _createTransaction = createTransaction;
    }

    public T Create()
    {
        return (T)_createTransaction(typeof(T));
    }
}

Container setup code utilising same:

public class DependencyRegistration : Registry
{
    public DependencyRegistration()
    {
        Scan(x =>
        {
            x.AssembliesFromApplicationBaseDirectory();
            x.WithDefaultConventions();
            x.AddAllTypesOf(typeof(Repository<>));
            x.ConnectImplementationsToTypesClosing(typeof(IRepository<>));
        });

        Scan(x =>
        {
            x.AssembliesFromApplicationBaseDirectory();
            x.AddAllTypesOf<IBusinessTransaction>();
            For(typeof(BusinessTransactionFactory<>)).Use(typeof(BusinessTransactionFactory<>));
            For<Func<Type, IBusinessTransaction>>().Use(type => (IBusinessTransaction)ObjectFactory.GetInstance(type));
        });


        For<ObjectContext>().Use(() => new ManagementEntities());
    }
}

What do you think?

like image 474
Rookian Avatar asked Nov 27 '11 13:11

Rookian


4 Answers

Mechanics

On the mechanical level, it's perfectly fine to used delegates, since a delegate is basically an anonymous Role Interface. In other words, whether you inject a delegate or interface or abstract base class doesn't really matter.

Concepts

On the conceptual level it's important to keep in mind the purpose of Dependency Injection. You may be using DI for different reasons than me, but IMO the purpose of DI is to improve the maintainability of a code base.

Whether or not this goal is accomplished by injecting delegates instead of interfaces is doubtful.

Delegates as dependencies

The first concern is about how well a delegate communicates intent. Sometimes an interface name communicates intent in itself, whereas a standard delegate type hardly does.

As an example, the type alone doesn't communicate much intent here:

public BusinessTransactionFactory(Func<Type, IBusinessTransaction> createTranscation)

Luckily, the createTranscation name still implies the role played by the delegate, but just consider (for argument's sake) how readable that constructor would have been if the author had been less careful:

public BusinessTransactionFactory(Func<Type, IBusinessTransaction> func)

In other words, using delegates shifts the focus from the name of the type to the name of argument. That's not necessarily a problem - I'm just pointing out that you need to be aware of this shift.

Discoverability versus Composability

Another concern is about discoverability versus composability when it comes to the types implementing the dependencies. As an examples, both of these implement public Func<Type, IBusinessTransaction>:

t => new MyBusinessTransaction()

and

public class MyBusinessTransactionFactory
{
    public IBusinessTransaction Create(Type t)
    {
        return new MyBusinessTransaction();
    }
}

However, in the case of a class, it's almost incidental that the concrete non-virtual Create method matches the desired delegate. It's very composable, but not very discoverable.

On the other hand, when we use interfaces, classes become part of is-a relationships when they implement interfaces, so it generally becomes easier to find all implementers and group and compose them accordingly.

Note that this is not only the case for programmers reading code, but also for DI Containers. Thus, it's easier to implement Convention over Configuration when you use interfaces.

1:1 interfaces versus the RAP

Some people have noticed that when attempting to use DI they end up with a lot of 1:1 interfaces (e.g. IFoo and a corresponding Foo class). In these cases, the interface (IFoo) seems redundant and it seems tempting to just get rid of the interface and use a delegate instead.

However, many 1:1 interfaces are really a symptom of a violation of the Reused Abstractions Principle. When you refactor a code base to reuse the same abstraction in multiple places, it makes sense to explicitly model the role of that abstraction as an interface (or abstract base class).

Conclusion

Interfaces are more than mere mechanics. They explicitly model roles in an application code base. Central roles should be represented by interfaces, whereas one-off factories and their ilk can be consumed and implemented as delegates.

like image 157
Mark Seemann Avatar answered Oct 19 '22 07:10

Mark Seemann


I personally don't like to see Func<T> arguments as dependencies in my classes, because I think it makes the code less readable, but I know many developers don't agree with me. Some containers, such as Autofac, even allows you to resolve Func<T> delegates (by returning a () => container.Resolve<T>()).

There are however, two cases where I don't mind to inject Func<T> delegates:

  1. When the class that takes the Func<T> constructor argument is located in the Composition Root, because in that case it's part of the container's configuration and I don't consider that part of the design of the application.
  2. When that Func<T> is injected in a single type in the application. When more services start depending on that same Func<T>, it would be better for readability to create a special I[SomeType]Factory interface and inject that.
like image 37
Steven Avatar answered Oct 19 '22 06:10

Steven


In the past, I have used both the pattern you mention (a delegate) and a single method interface. Currently, I tend to prefer the use of a single method interface.

The code required to mock the dependency is easier to read when using an interface and you will also have the ability to take advantage of automatically typed factories, as @devdigital mentions.

Another thing to consider is that you won't have the overhead of a delegate invocation when using an interface, which could be a valid consideration in very high performance applications.

like image 42
Adam Ralph Avatar answered Oct 19 '22 05:10

Adam Ralph


@Steven's answer is very well thought out; personally I find limited use of Func<T> arguments readable.

What I don't understand is the value of BusinessTransactionFactory<T> and IBusinessTransactionFactory<T>. These are just wrappers around the delegate. Presumably you're injecting IBusinessTransactionFactory<T> somewhere else. Why not just inject the delegate there?

I think of Func<T> (and Func<T, TResult>, etc.) as factories. To me you have a factory class implementing a factory interface wrapping a factory delegate, and that seems redundant.

like image 23
TrueWill Avatar answered Oct 19 '22 07:10

TrueWill