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>();