Wednesday, June 25, 2014

A Map of Factories for the Factory Method Pattern


Sometimes there is no substitute for doing something stupid—just to see why it is stupid. It is one thing to read that a thing is stupid, but that tactile feeling of failure leaves an imprint that sticks with a person like a quality case of food poisoning. The body may expel the immediate repercussions of food poisoning, but it will be a long time before you try that particular Mediterranean bistro again.

Toward that end I have been doing dumb things with mirrors, named constructors and default implementation approaches to the Factory Method pattern in Dart. Actually not all of those are dumb—it depends on the context in which they are used. And that's going to be a big part of my job in Design Patterns in Dart—figuring out which approaches are decent in the right circumstances and which are the coding equivalent of food poisoning.

Also, I need to come up with really good food poisoning descriptions so that I can fully imprint on the hearty reader which approaches truly are stupid (and why). But that's a skill I can develop later. For now, I try another approach to the Factory Method pattern. This one promises to be rather good...

I continue to work with the Hipster MVC framework. It is a toy MVC framework for the web (built primarily to support Dart for Hipsters). Toy or not, frameworks are fertile ground for raising robust Factory Method patterns. In this case, it has an abstract product, HipsterModel (the “M” in this framework's MVC). It also has an abstract creator, HipsterCollection. The current implementation of the Factory Method in this creator class is the modelMaker():
abstract class HipsterCollection extends IterableBase {
  HipsterModel modelMaker(Map attrs);
  // ...
}
This abstract method is declared so that subclasses will define a method that accepts a Map of attributes and returns a concrete product of type HipsterModel. The concrete creator implementation for a comic book collection (like the one in the Dart Comics application) might look something like:
class Comics extends HipsterCollection {
  modelMaker(attrs) => new ComicBook(attrs);
  String url = '/comics';
}
This is a classic implementation of the Factory Method pattern, done in Dart. It is particularly effective in this framework example. The intent is to maintain a relationship between the creator class (HipsterCollection) and the product class (HipsterModel). Since the framework has no business knowing the concrete class types (ComicBook), using Factory Method defers the concrete instantiation to the concrete creator class.

This approach works pretty darn well for a framework, but there are other approaches to the pattern. An interesting one was suggested by Vadim Tsushko in the comments of a previous post. In Vadim's approach, a subclass of the creator is not responsible for creating concrete products. Instead, that responsibility goes into a Map:
typedef HipsterModel modelMaker(Map attrs);
Map<Type, modelMaker> factory = new Map();
The factory map above is keyed by a Type and returns functions of the modelMaker type. In this approach, modelMaker is typedef'd as a function that accepts a map of attributes and returns an object of type HipsterModel. In other words, modelMaker functions have the exact same signature that my current modelMaker() method has.

To explore this approach in Hipster MVC, I declare the typedef in the collection class and add the factory as a static class variable on the same:
typedef HipsterModel modelMaker(Map attrs);

class HipsterCollection extends IterableBase {
  // ...
  static Map<Type, modelMaker> factory = new Map();

  HipsterModel modelMaker(Map attrs) => factory[this.runtimeType](attrs);
  // ...
}
The modelMaker() method is no longer abstract. Its definition returns the result of invoking the factory for the current runtime type.

The factory for the Comics collection can be added as:
HipsterCollection.factory[Comics] = (attrs) => new Models.ComicBook(attrs);
Once that is registered, an instance of Comics that needs a model (e.g. if it GETs a record via a REST backend) can get one by invoking modelMaker with the attributes. This will lookup the correct factory based on the object's runtimeType, which is Comics. The anonymous function associated with this type will be invoked with the supplied attributes and the result returned.

I rather like that. The implementation is succinct. The resulting code might be a little hard to read, but the associated typedef and Map types clear things up nicely. The result is a subclass of the creator that does no creating itself:
class Comics extends HipsterCollection {
  String url = '/comics';
  // NO LONGER NEEDED :)
  // modelMaker(attrs) => new ComicBook(attrs);
}
The only downside is also a potential benefit, depending on the context. In the case of my framework, I had to assign the HipsterCollection.factory in the main() entry point:
main() {
  HipsterCollection.factory[Comics] = (attrs) => new Models.ComicBook(attrs);
  // ...
}
In certain circumstances this could be a benefit—keeping all of the factory registration in on place. In the case of my framework, this is probably not a good practice. Since I have to subclass the creator class anyway, I might as well put the creator method in that subclass. Even so, I like the factory map approach enough that I will likely include it in the book.


Day #103

1 comment:

  1. Glad you tried it. I consider it good candidate to include in collection of patterns. I believe I saw it in the wild several times - in John Evans `Buckshot UI` Framework as a one example IIRC. I myself use it in objectory - (my version of toy project, object to document mapper on top of mongodb).
    BTW currenlty it is much more pleasant in implementation. When we started to use it int Dart there was no support for storing Type as reference, no `this.runtimeType` and so on. So we have have to include String type description getters into each of models classes `String get type => 'ComicBook'` and maintain map of Strings to anonymous functions. That was much worse to read.
    Introduction of first class Types made some framework's code much more readable.
    AFAIK there is approved bug to add access to static members of classes from Type reference. If that bug would be fixed, that pattern would probably be redundant and we would use factory method pattern in it's original Smalltalk version.

    ReplyDelete