2
votes

I know how I can inject one or a collection of dependency interface instances into a class via constructor injection. However, in my current situation I have a bit different task.

I have several classes, and each of them has an associated "Processor" class. These processors are implementing the same IProcessor interface, and a common Processor class will process a collection of objects, using the appropriate processors for each of them. Creating a processor for a type can be expensive, so I'm using factories and instantiate the processor only when it's needed.

The code would look something like this.

public interface IProcessor {
  void Process(object item);
}

public class Processor {

  private readonly Dictionary<Type, Func<IProcessor>> _processors;

  public Processor(IDictionary<Type, Func<IProcessor>> processors) {
    _processors = processors;
  }

  public void Process(IEnumerable items) {
    foreach (var item in items) {
      var processorFactory = _processors.GetValueOrDefault(item.GetType());
      if (processorFactory == null) continue; // for simplicity
      var processor = processorFactory();
      processor.Process(item);
    }
  }

}

How could I register the binding for this in Ninject? Or is there any kind of alternative patterns which are more "DI friendly"?

I would like to configure these "processor bindings" at application entry point level.

An alternative would be to have a static dictionary of processor factories in the Processor class, and register the bindings manually at the entry point, but I would like to avoid using static dependencies. Or would it be still better in this particular case?

UPDATE

Another, kind of hybrid alternative which I arrived to is something like this. I would have a static Factories dictionary in the Processor class. There I could have basic, default implementations as a facade.

Then in my Ninject module I could write something like this.

public class MyModule : NinjectModule
{
  public override void Load()
  {
    // ... my "standard" bindings

    Processor.Factories[typeof(MyItem1)] = () => Kernel.Get<MyItem1Processor>();
    Processor.Factories[typeof(MyItem2)] = () => Kernel.Get<MyItem2Processor>();
  }
}

I know that I'm using the "evil" static stuff here, but still can utilize DI quite easily and in a well readable way, utilizing the Kernel property of the module.

Is it safe to use the Kernel property of the module inside the Load method? I mean can a module be loaded into more kernels for example?

Any thoughts are appreciated.

2
No problem. After having read your full question again, you basically do exactly the same as what I do in this case. Except I usually create a IProcessor and have the Type returned from the interface, this means that I can bind each IProcessor and then simply have IEnumerable<IProcessor> in the constructor. From that I build a dictionary of <Type, IProcessor>Stephen Ross
@StephenRoss I need the lazy instantiation of procesors on demand. For example I don't want to instantiate the processor for Animal type if there are no animals in the collection to process. Otherwise I would do the same and I would have no problems.Zoltán Tamási
Yeah I had thought about that. Is it possible to use the lazy factory method for Ninject github.com/ninject/Ninject.Extensions.Factory/wiki/Lazy. This is provided by a separate extension if that's possible in your project.Stephen Ross
@StephenRoss I know about that, but I would still need the dictionary to figure out which processor to use. If the type would be returned by the processors, I would have to instantiate them anyway.Zoltán Tamási
How does the IProcessor interface look like?Yacoub Massad

2 Answers

2
votes

I'm answering my question with my final solution.

I believe, that in software development process if something "doesn't want to be put together", then it's an indication of some smell and most of the times I needed to get back a few levels to find it. It's something similar here too.

I realized that it's not a good design to use the factory pattern in this scenario because:

  1. I think instantiating a processor object should never be so expensive, as each processor object should optimize their resources to use them only if Process is called.
  2. Even if instantiation is expensive, with my original pattern, whenever a suitable object is in the processed list, a new instance is created. (This could be handled by the processor, but still doesn't look good at all.)
  3. No way to add custom processors with respect to priority. Let's say ProcessorA processes ClassA, ClassB extends ClassA and ProcessorB processes ClassB. I have no way to prevent ProcessorA to process ClassB and allow other (non custom processed) ClassA descendandts to be still processed with ProcessorA at the same time. This is because of the Dictionary structure.

So I decided to simplify the implementation and pass an enumerable of IProcessors directly to the main processor, and have a CanProcess(object obj) in IProcessor. This way I can directly use any DI container to inject a list of all bound implementations.

1
votes

If you want lazy initialization, how about a factory class rather than a Func?

Have a base factory class:

public abstract class ProcessorFactory
{
    public abstract Type ItemType { get; }
    public abstract IProcessor GetProcessor();
}

Create a concrete instance of the class for each item type and inject a collection of those into your constructor. Then build your dictionary from that:

public class Processor
{
    private readonly Dictionary<Type, ProcessorFactory> _processors;

    public Processor(IEnumerable<ProcessorFactory> processors)
    {
        _processors = processors.ToDictionary<ProcessorFactory, Type>(p => p.ItemType);
    }

    public void Process(IEnumerable items)
    {
        foreach (var item in items)
        {
            var processorFactory = _processors.GetValueOrDefault(item.GetType());
            if (processorFactory == null) continue; // for simplicity
            var processor = processorFactory.GetProcessor();
            processor.Process(item);
        }
    }
}

Update 1

Here's sample code for the full factory implementation:

First I changed the factory to an interface:

public interface IProcessorFactory
{
    Type ItemType { get; }
    IProcessor GetProcessor();
}

Then I created an abstract generic base class for the factories:

public abstract class ProcessorFactoryBase<TItem> : IProcessorFactory
{
    private Lazy<IProcessor> _factory;

    public ProcessorFactoryBase(Func<IProcessor> factory)
    {
        _factory = new Lazy<IProcessor>(factory);
    }

    public Type ItemType
    {
        get { return typeof(TItem); }
    }

    public IProcessor GetProcessor()
    {
        return _factory.Value;
    }
}

To create a factory, simply inherit from the base with the appropriate item type and implement the constructor:

public class ProcessorFactoryA : ProcessorFactoryBase<ItemA>
{
    public ProcessorFactoryA(Func<IProcessor> factory) : base(factory) { }
}

Note that the factory class is tied to the item type; the processor type is injected via the bindings:

public class Bindings : NinjectModule
{
    public override void Load()
    {
        Bind<IProcessorFactory>().ToMethod(context => new ProcessorFactoryA(() => context.Kernel.Get<ProcessorX>()));
        Bind<IProcessorFactory>().ToMethod(context => new ProcessorFactoryB(() => context.Kernel.Get<ProcessorY>()));
        Bind<IProcessorFactory>().ToMethod(context => new ProcessorFactoryC(() => context.Kernel.Get<ProcessorZ>()));
        // Note that item type D is handled by processor X
        Bind<IProcessorFactory>().ToMethod(context => new ProcessorFactoryD(() => context.Kernel.Get<ProcessorX>()));
    }
}

I made a .NET fiddle with the full working code: http://dotnetfiddle.net/aD9E2y.

It has an error when you try to run the fiddle, but you can just grab the code into a .NET console project and it runs.

Some people don't like them, but I've used T4 templates to do things like automatically generating the processor factory classes using reflection. The bindings will still have to be manually created, however, because the association between item type and processor can't be inferred.