Advanced factory support

Jun 13, 2013 at 2:27 PM
Edited Jun 13, 2013 at 2:35 PM
In my applications, I make quite heavy use of factories for two purposes:
  1. Decoupling the consumer of the dependency from the concrete implementation when the consumer needs to control the lifetime of the dependency.
  2. Move the resolution of dependencies of the dependency to a central place and don't burden the consumer with them in scenarios where the consumer needs to supply parts of the dependencies. In this scenario there even might be tight coupling - think View Models.
An example for the second purpose could look like so:
public class DialogViewModel
{
    public DialogViewModel(ICommandHandler<AcceptDataCommand> acceptData,
        object parent, string dataToDisplay)
    { /* ... */ }
}

public class DialogFactory
{
    ICommandHandler<AcceptDataCommand> _acceptData;
    
    public DialogFactory(ICommandHandler<AcceptDataCommand> acceptData)
    {
        _acceptData = acceptData;
    }
    
    public DialogViewModel Create(object parent, string dataToDisplay)
    {
        return new DialogViewModel(_acceptData, parent, dataToDisplay);
    }
}

public class MainViewModel
{
    public MainViewModel(DialogFactory factory) { /* ... */ }
    
    public void DoSomething()
    {
        factory.Create(this, UserEnteredData).Show();
    }
}
That factory is pure infrastructure, it contains no logic at all.
Autofac has a feature that automatically generates factories that can take parameters. This can either be a Func<TIn, TResult> or a custom delegate. The custom delegate has the advantage that it can give the parameters speaking names and even provide documentation. Using that feature, we can get rid of the factory class and instead create a factory delegate:
public delegate DialogViewModel DialogFactory(object parent, string dataToDisplay);
The implementation of MainViewModel would now like this:
public MainViewModel(DialogFactory factory) { /* ... */ }

public void DoSomething()
{
    factory(this, UserEnteredData).Show();
}
What I like so much about this feature is that it removes the need for plumbing (the factory implementation without any logic) while not losing any expressiveness (the custom delegate with parameter names and documentation).

I am pretty sure that I could build support for this - starting point would be this.

However, before doing so I wanted to hear your opinion about this approach. If I would implement something like this, would you consider a pull request for it or are there reasons why you would decline it independently from the concrete implementation quality?
Coordinator
Jun 13, 2013 at 3:43 PM
IMO, such feature should not be part of the core library, and I won't accept a pull request that changes the core lirary to do this. In implementation of this that uses the ResolveUnregisteredType event and placed in the Code Samples project would be accepted though. The Code Samples project is the place where all crazy stuff is placed that is not suited for the core library, because its quality is too low, its incomplete, its complicates the API, its an uncommon feature, or promotes bad practice.

In your specific case, you are injecting services into your View Model, which can be considered bad practice. This stackoverflow question seems related . Your View Model should be a dumb DTO and your View should be a humble object that just maps everything in the View Model. Neither the view, nor the view model should trigger any actions. The Controller or Presenter should present the View with all the data it needs.

If you follow that approach, the controller/presenter can simply new up that View Model (DTO) and you don't need to inject anything. Further more, even if you still want to inject a service, those factories still seem redundant, because the presenter can new up the View Model itself. No need to have a factory for that.

I hope this makes sense.
Marked as answer by dot_NET_Junkie on 2/26/2014 at 1:37 PM
Jun 13, 2013 at 3:53 PM
Edited Jun 13, 2013 at 4:15 PM
In the context of WPF and MVVM, the ViewModel is the controller / presenter.
It contains the actions that are executed when the user clicks buttons etc. The method DoSomething from the sample above would be automatically mapped to a button with the same name on the associated view with the help of using Caliburn.Micro.
I don't see how you would do MVVM with dumb ViewModels that don't require any dependencies.

Am I missing something here or are we talking about different kinds of ViewModels?
Coordinator
Jun 13, 2013 at 9:04 PM
Take a look at the AllowResolvingParameterizedFuncFactories extension method in the source code of the Code Samples project.
Jun 14, 2013 at 11:56 AM
I added support for arbitrary, parameterized delegates with the same functionality as the Func support.

The changes were rather simple.

Some helper query methods:
    private static IEnumerable<Type> GetDelegateParameters(Type delegateType)
    {
        return GetInvokeMethodSafe(delegateType).GetParameters().Select(x => x.ParameterType);
    }

    private static Type GetDelegateReturnType(Type delegateType)
    {
        return GetInvokeMethodSafe(delegateType).ReturnType;
    }

    private static MethodInfo GetInvokeMethod(Type delegateType)
    {
        return delegateType.GetMethod("Invoke");
    }

    private static MethodInfo GetInvokeMethodSafe(Type delegateType)
    {
        var invokeMethod = GetInvokeMethod(delegateType);
        if (invokeMethod == null)
            throw new InvalidOperationException(string.Format("'{0}' is not a valid delegate.", delegateType));
        return invokeMethod;
    }
IsParameterizedFuncDelegate has been renamed and now looks like this:
    private static bool IsParameterizedDelegate(Type type)
    {
        if (type.BaseType != typeof(MulticastDelegate))
            return false;

        return GetInvokeMethod(type) != null && GetDelegateReturnType(type) != typeof(void)
                && GetDelegateParameters(type).Any();
    }
AllowResolvingParameterizedFuncFactories has been renamed to AllowResolvingParameterizedDelegateFactories.

The lines to get the component type and the factory arguments have changed:
    var delegateType = e.UnregisteredServiceType;
    // ...
    var componentType = GetDelegateReturnType(delegateType);
    // ...
    var factoryArguments = GetDelegateParameters(delegateType);
For now, the unit tests are simply copy & paste of the existing ones with the Act part changed as follows:
var tupleFactory = container.GetInstance<FactoryDelegate<Tuple<int, string>>>();
The used FactoryDelegate<TReturn> looks like this:
public delegate TReturn FactoryDelegate<out TReturn>(int i, string s);
Obviously, those simple changes didn't implement parameter matching by name.
Coordinator
Jun 14, 2013 at 12:44 PM
Looks good. I'll try to incorperate this in the Code Samples.
hose simple changes didn't implement parameter matching by name.
I'm not sure that matching parameter names would be a good idea. That sounds really fragile.
Jun 14, 2013 at 12:48 PM
Edited Jun 14, 2013 at 12:51 PM
I am not sure either, that's why I haven't done it yet. But I think that the current implementation using a sequence is just as fragile. Both approaches require an implicit relationship between the constructor and the factory parameters.

One possibility would be to allow the user to select the strategy (by name, by sequence or by type for delegates and by sequence or by type for Func), just like for constructors. Like that, he would at least know what he is getting.

But honestly, I don't need it currently and thus, the effort required to implement it is not justified.
Coordinator
Jun 14, 2013 at 12:53 PM
I hope you understand now why I feel it's not suited as feature of the core library.
Jun 14, 2013 at 12:58 PM
Indeed, I do.