Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ninject, the "Abstract Factory" pattern, and runtime conditional resolution

Introduction

I have been reading through the Ninject documentation, I reached the part where it talks about Factories (check http://www.ninject.org/wiki.html or http://www.planetgeek.ch/2011/12/31/ninject-extensions-factory-introduction/). There the Abstract Factory pattern is being referenced (Wikipedia).

I keep finding a discrepancy between the way that the pattern is described in the Wikipedia article and the Ninject examples. I have also searched on SO and read a few answers relevant to this subject, and I still observe similarity in diverging from how it is described in Wikipedia.

Details

In Wikipedia

Class Diagram You can notice:

  • there are multiple implementations *Concrete Factory* for the abstract factory.
  • there are multiple implementations *Concrete Product* of the abstract product.
  • each concrete factory generates a concrete product. In the diagram WinFactory generates WinButton and OSXFactory generates OSXButton
  • if I am to write a program that determines types conditionally at runtime, it is obvious that I would have multiple implementations of a common abstraction (in the diagram multiple implementations of the Button interface)
  • if I am to use the Abstract Factory pattern to achieve that, then according to the Wikipedia article I am deducing that at least one way _ the article does not show another way to do it _ would be to conditionally resolve to one of the multiple implementations for the factory which in turn would give me one of the multiple implementations of the Product

In the Ninject documentation

We have:

public class Foo
{
    readonly IBarFactory barFactory;

    public Foo(IBarFactory barFactory)
    {
        this.barFactory = barFactory;
    }

    public void Do()
    {
        var bar = this.barFactory.CreateBar();
        ...
    }
}

public interface IBarFactory
{
    Bar CreateBar();
}

and

kernel.Bind<IBarFactory>().ToFactory();
  • I don't see multiple implementations of neither the factory nor the product
  • Other than allowing for code like this var bar = this.barFactory.CreateBar(); rather than have the dependency injected through the constructor I do not see the point. There might be a use for being able to use code like this (examples?) but is that all there is to it?

On SO

  • I saw this. Check the last comment which seems to indicate having multiple methods within the factory returning different implementations, but still we are using only one concrete factory and thus not following the Wikipedia article
  • This seems to resemble Ninject's examples
  • In this a dependency is used, but still the type is not abstract

The question

Are the examples other than the Wikipedia one really following the Abstract Factory pattern?

like image 678
MSD Avatar asked Jan 22 '14 14:01

MSD


Video Answer


1 Answers

TL;DR

Are the (Ninject) examples other than the Wikipedia one really following the Abstract Factory pattern?

In concept, yes, IoC containers like Ninject allow (in the spirit of) the original objectives (and more) of Abstract Factory, but in implementation, no, a modern application using an IoC container like Ninject doesn't require the myriad of concrete factory classes - which typically do nothing else than new() the concrete instance of the type for which they are built - especially when used in garbage-collected environments like the JVM and managed .Net.

IoC Containers have tools like reflection, factory functions / lambdas, and even dynamic languages to create concrete classes. This includes allowing additional creation strategies, such as allowing for discrimination on parameters and context of the invocation.

Instead of fixating on the original code class implementation of the GoF patterns, I would suggest focusing instead on the high level concept of each GoF pattern, and the problem that each is intended to solve.

Rationale

Many of the Gang of Four patterns (like Abstract Factory) are frequently either absorbed into, or can be simplified in modern languages and frameworks - i.e. evolutionary language and design improvements since the mid 1990's have in many instances meant that the core GoF pattern concept can be implemented more concisely, and in some cases may make several of the code and UML classes in the original GoF book redundant.

e.g. in C#,

  • Iterator is frequently incorporated directly into compilers (foreach / GetEnumerator())
  • Observer comes standard with multicast delegate and events, etc.
  • With Singleton, rather than using static instancing, we would typically use an IoC to manage the singleton. The decision whether to manage the lifespan with lazy instancing would be a separate concern altogether. (and we have Lazy<T> for that, including handling the thread safety issue not forseen in GoF)
  • I believe the same is true in many cases for Abstract Factory and Factory Method, when an IoC container is available.

However, the concepts of all of the GoF design patterns are still as important today as ever.

For the various creational GoF patterns, when the Gang of Four book was written, IoC Containers like Ninject weren't widely used in the mainstream yet. Also, most languages in the mid-90's didn't have garbage collection - as a result, classes dependent on others ("Dependent classes") had to manage both the resolution, and control the lifespan, of dependencies, and this may help to explain why explicit factories were much more common in the 90's than today.

Here's some examples:

If a factory is used simply to abstract the creation, and / or allow a configurable strategy to resolve a single dependency, and where no special dependency lifespan control is needed, then the factory can be avoided altogether and the dependency can be left to the IoC container to build up.

e.g. In the Wiki example provided by the OP, it is likely that the strategy (decision) of whether to build a WinFormsButton or an OSXButton would be a an application configuration, which would be fixed for the lifetime of the application process.

GoF style Example

For a Windows and OSX implementation, would require the ICanvas and ICanvasFactory interfaces, and an additional 4 classes - OSX and Windows Canvasses, plus FactoryClasses for both. The problem of strategy, i.e. which CanvasFactory to resolve would also need to be addressed.

public class Screen
{
    private readonly ICanvas _canvas;
    public Screen(ICanvasFactory canvasFactory)
    {
        _canvas = canvasFactory.Create();
    }

    public ~Screen()
    {
        // Potentially release _canvas resources here.
    }
}

Modern IoC era Example of Simple Factory Method

If the decision on the concrete class does not need to be determined dynamically at run time, the factory can be avoided altogether. The dependent class can simply accept an instance of dependency abstraction.

public class Screen
{
    private readonly ICanvas _canvas;
    public Screen(ICanvas canvas)
    {
        _canvas = canvas;
    }
}

And then all that is needed is to configure this in the IoC bootstrapping:

if (config.Platform == "Windows")
    // Instancing can also be controlled here, e.g. Singleton, per Request, per Thread, etc
    kernel.Bind<ICanvas>().To<WindowsCanvas>(); 
else
    kernel.Bind<ICanvas>().To<OSXCanvas>();

We would thus only need one interface, plus the two concrete WindowsCanvas and OSXCanvas classes. The strategy would be resolved in the IoC bootstrapping (e.g. Ninject Module.Load) Ninject is now responsible for the lifespan of the ICanvas instance injected into the dependent class.

IoC Replacement of Abstract Factory

There are however still some occurrences in modern day C# where a class would still need a dependency Factory, rather than just an injected instance, e.g.

  • When the number of instances to be created is unknown / dynamically determined (e.g. a Screen class may allow for dynamic addition of multiple buttons)
  • When the dependency class should NOT have a prolonged lifespan - e.g. where it is important to release any resources owned by dependency created (e.g. the dependency implements IDisposable)
  • When the dependency instance is both expensive to create, and may not actually be needed at all - see Lazy Initialization patterns like Lazy

Even so, there are simplifications using IoC containers which can avoid proliferation of multiple factory classes.

  • Abstract Factory interfaces (e.g. GUIFactory in the Wiki example) can be simplified to use lambdas Func<discriminants, TReturn> - i.e. because Factory typically has only one public method Create(), there is no need to build factory interfaces or concrete classes. e.g.

    Bind<Func<ButtonType, IButton>>()
        .ToMethod(
            context =>
            {
                return (buttonType =>
                {
                    switch (buttonType)
                    {
                        case ButtonType.OKButton:
                            return new OkButton();
                        case ButtonType.CancelButton:
                            return new CancelButton();
                        case ButtonType.ExitButton:
                            return new ExitButton();
                        default:
                            throw new ArgumentException("buttonType");
                    }
                });
            });
    

The abstract factory can be replaced with the Func resolver

public class Screen
{
    private readonly Func<ButtonType, IButton> _buttonResolver;
    private readonly IList<IButton> _buttons;
    public Screen(Func<ButtonType, IButton> buttonResolver)
    {
        _buttonResolver = buttonResolver;
        _buttons = new List<IButton>();
    }

    public void AddButton(ButtonType type)
    {
        // Type is an abstraction assisting the resolver to determine the concrete type
        var newButton = _buttonResolver(type);
        _buttons.Add(newButton);
    }
}

Although in the above, we've simply used an enum to abstract the creation strategy, IoC frameworks allow the abstraction of the concrete creation 'discrimination' to be specified in a number of ways, such as by named abstraction, by attributes (not recommended - this pollutes the dependent code), tied to the context, such as by inspection of other parameters, or to the dependent class type, etc.

It is also worth noting that IoC containers can also assist when the dependency themsleves ALSO have further dependencies requiring resolution (possibly again, using an abstraction). In this case, the new can be avoided and the build of each button type again resolved through the container. e.g. the above bootstrapping code could also be specified as :

 case ButtonType.ExitButton:
      return KernelInstance.Get<OkButton>();
like image 74
StuartLC Avatar answered Sep 22 '22 12:09

StuartLC