Skip to content

StyletIoC Factories

cH40z-Lord edited this page Dec 26, 2014 · 2 revisions

The Problem

Constructor / parameter injection is all well and good, so long as you only need one instance of something. When you start needing to create instances yourself (think your ShellViewModel wanting to show dialogs, and needing to create ViewModels for them), i.e.

class ShellViewModel
{
   // ...
   public void ShowDialog()
   {
      // We don't want to create DialogViewModel directly, as it has its own dependencies which need injecting
      var dialogVm = new DialogViewModel();
      this.windowManager.ShowDialog(dialogVm);
   }
}

The tempting solution us to make your ShellViewModel (or whatever) aware of your IoC container, so it can call this.container.Get<DialogViewModel>(), like this:

class ShellViewModel
{
   // ...
   public ShellViewModel(IContainer container)
   {
      this.container = container;
   }

   public void ShowDialog()
   {
      var dialogVm = this.container.Get<DialogViewModel>();
      this.windowManager.ShowDialog(dialogVm);
   }
}

This is known as the Service Locator Pattern, and there's a lot of debate as to whether it's actually an anti-pattern - essentially, it adds a dependency that shouldn't be added (your class needs to know about your (correctly configured) IoC container) - while hiding dependencies that your class actually does have.

The Solution

This problem was actually solved in 1994 by the GoF - the Abstract Factory Pattern. Since you can't inject a DialogViewModel into your ShellViewModel, you can instead inject a factory which can create DialogViewModels, like this:

class ShellViewModel
{
   // ...
   public ShellViewModel(Func<DialogViewModel> dialogViewModelFactory, IWindowManager windowManager)
   {
      this.dialogViewModelFactory = dialogViewModelFactory;
      this.windowManager = windowManager;
   }

   public void ShowDialog()
   {
      var dialogVm = this.dialogViewModelFactory();
      this.windowManager.ShowDialog(dialogVm);
   }
}

This works really well: you can provide whatever you like here for testing, and the IoC container can provide something suitable for normal operation.

StyletIoC supports two sorts of factories: "Func-factories" - like the one shown above - and "abstract factories" - where you provide an interface for the factory and StyletIoC provides the implementation. Both are documented in their own sections below.

Func Factories

These are the easiest types of factories to use. They require no setup on your part, and they're quite flexible. They do however have a couple of limitations: these are discussed later, and are solved by abstract factories.

The golden rule is this: If you can execute container.Get<ISomeInterface>(), you can also execute container.Get<Func<ISomeInterface>(). The same goes of course for constructor and property injection: if you can write a constructor like this:

class Foo
{
   public Foo(IBar bar)
   {
   }
}

You can also write this:

class Foo
{
   public Foo(Func<IBar> bar)
   {
   }
}

If you execute a func factory Func<ISomeInterface> multiple times, the effect is the same as if you'd called container.Get<ISomeInterface>() multiple times: if ISomeInterface was registered as a singleton, you'll get the same instance back each time. If it was registered as a transient, you'll get a different instance back each time.

Func factories also have some other tricks up their sleeves: If you request a Func<IEnumerable<ISomeInterface>>, (either using container.Get<Func<IEnumerable<ISomeInterface>>>(), or through constructor/property injection), you'll get a factory that, when called, will return the same thing as container.GetAll<ISomeInterface>() (or if you'd injected IEnumerable<ISomeInterface> into your constructor/property).

Similarly, if you request an IEnumerable<Func<ISomeInterface>> (either through container.GetAll<Func<ISomeInterface>()), or by injecting IEnumerable<Func<ISomeInterface>> into a constructor or property) you'll get a collection of factories, and each one will be able to create a new instance of that implementation of ISomeInterface.

Func factories do have a limitation: fetching stuff that requires a key. Recall that you can call container.Get<ISomeInterface>("magicKey")? While you can call var factory = container.Get<Func<ISomeInterface>>("magicKey"); factory(), you can't do something like var factory = container.Get<Func<string, ISomeInterface>>(); factory("magicKey"). This is a deliberate decision - it's all a bit non-obvious to start with, and there are some really tricky corner-cases when you start getting involved with collections of factories and factories of collections. If you need this behaviour, use an Abstract Factory (below).

Abstract Factories

Abstract factories are similar to func factories, but instead of every factory having the type Func<ISomeInterface>, you instead define your own factory.

Consider this example:

interface IDialogViewModelFactory
{
   DialogViewModel CreateDialogViewModel();
}

class ShellViewModel
{
   // ...
   public ShellViewModel(IDialogViewModelFactory dialogViewModelFactory)
   {
      this.dialogViewModelFactory = dialogViewModelFactory;
   }

   public void ShowDialog()
   {
      var dialogVm = this.dialogViewModelFactory.CreateDialogViewModel();
      this.windowManager.ShowDialog(dialogVm);
   }
}

In the most basic case, you can provide a simple implementation of IDialogViewModelFactory which just calls into the IoC container for production use, or you can provide a mock for testing.

But writing that implementation of IDialogViewModelFactory is a bit of a pain, and one which StyletIoC has a solution for. Like a few other IoC containers, StyletIoC is capable of taking an interface like this, and generating an implementation for you (the difference being that StyletIoC does not require any dependencies to do this).

To take advantage of this, use the binding syntax builder.Bind<IFactoryInterfaceType>().ToAbstractFactory(), for example:

public interface IDialogViewModelFactory
{
   DialogViewModel CreateDialogViewModel();
}

// ... 

builder.Bind<IDialogViewModelFactory>().ToAbstractFactory();

Such factory methods can also create collections of types, for example:

public interface IVehicleFactory
{
   IEnumerable<IVehicle> CreateVehicleCollection();
}

Restrictions

There are a few things to bear in mind when writing your factory interface. These are expanded on below:

  1. The interface must be public, or you must have [assembly: InternalsVisibleTo(StyletIoC.FactoryAssemblyName)] in your AssemblyInfo.cs.
  2. Every method in the factory must return a type which is registered with the IoC container, or an IEnumerable of that type, and must have either zero parameters, or a single string parameter.

StyletIoC generates an implementation of the factory interface in a different assembly, so your interface must either be public, or you must have marked this assembly as being a 'friend' of yours (using [assembly: InternalsVisibleTo(StyletIoC.FactoryAssemblyName)]). You probably do want your factory interfaces to be public (so you can manually provide an implementation when writing your unit tests.

StyletIoC only knows how to generate method implementations for methods which return something (i.e. that aren't void), and which take zero parameters, or a single string parameter (used as a key, covered below).

Using Keys With Abstract Factories

There are a number of different ways you specify the key to use when StyletIoC's implementation creates a new instance of a type (see StyletIoC Keys). The first is using the [Inject(Key = "key")] attribute:

public interface IDialogViewModelFactory
{
   [Inject(Key = "someKey")]
   DialogViewModel CreateDialogViewModel();
}

You can also create methods which take a single string parameter, which will be used:

public interface IDialogViewModelFactory
{
   DialogViewModel CreateDialogViewModel(string key);
}

// ... then

this.dialogViewModel.CreateDialogViewModel("someKey");

Under The Hood

Under the hood, StyletIoC generates a type with an implementation which looks a lot like this:

public interface IFactoryInterface
{
   TypeWithoutKey CreateTypeWithoutKey();

   IEnumerable<ElementType> CreateCollection();

   [Inject(Key = "key")]
   TypeWithAttributeKey CreateTypeWithAttributeKey();

   TypeWithKeyParameter CreateTypeWithKeyParameter(string key);
}

public class FactoryInterface : IFactoryInterface
{
   private IContainer container;
   public FactoryInterface(IContainer container)
   {
      this.container = container;
   }

   public TypeWithoutKey CreateTypeWithoutKey()
   {
      return this.container.GetTypeOrAll(typeof(TypeWithoutKey));
   }

   public IEnumerable<ElementType> CreateCollection()
   {
      return this.container.GetTypeOrAll(typeof(IEnumerable<ElementType>));
   }

   public TypeWithAttributeKey CreateTypeWithAttributeKey()
   {
      return this.container.GetTypeOrAll(typeof(TypeWithAttributeKey), "key");
   }

   public TypeWithKeyParameter CreateTypeWithKeyParameter(string key)
   {
      return this.container.GetTypeOrAll(typeof(TypeWithKeyParameter), key);
   }
}
Clone this wiki locally