Thursday, June 26, 2014

Compile Time Constant Factory Method


Tonight I continue to poke and prod the first design pattern destined for Design Patterns in Dart...

I really like yesterday's map-of-factories approach to the Factory Method pattern in Dart. The approach, first suggested to me by Vadim Tsushko removes the immediate responsibility of Factory Methods from subclasses of the creator class and instead places them in a Map of factories. I think I may have left some room for improvement though...

The classic Factory Method pattern starts with an abstract creator class that includes an abstract creator method:
abstract class Creator {
  Product productMaker();
  // ...
}
The Product is similarly abstract—the concrete subclass of Product is deferred to the concrete subclass of Creator. Leaving the productMaker() method abstract says just that: the subclass needs to define a productMaker() method that returns a subclass of Product:
class ConcreteCreator extends Creator {
  productMaker()=> new ConcreteProduct();
}
Yesterday's variation does away with the creator subclass, which is quite nice if the factory method is the only reason to define a subclass. Instead, the top-level creator class now looks up the factory method in a simple Map:
typedef Product productFactory();
class Creator {
  static Map<Type, productFactory> factory = new Map();
  Product productMaker(type) => factory[type]();
  // ...
}
Here, I typedef a function that takes no arguments and returns some kind of concrete Product (just like the original productMaker() factory method). The static factory map is then a key/value store in which the keys are types and the values are product factories.

Either approach (subclass or map-of-factories) will work as a Factory Method implementation. I have yet to benchmark the two. In the absence of that admittedly useful information, the choice between the two comes down to context or personal preference. In the web MVC framework example that I used yesterday, it probably makes more sense to use the subclass approach as I have to create subclasses anyway and the map-of-factories was a little awkward. If the concrete classes are all part of the same codebase or if a parallel class hierarchy needs factories, then map-of-factories seems the better approach.

I do not want to dwell on those question too much just yet. Instead, I am wondering if including the map-of-factories in the creator class is the right approach. Perhaps this was just the codebase upon which I was experimenting last night, but it felt a little messy having it in the same class.

Instead, I would like an entirely separate Factory class to hold this information. Something along the lines of:
typedef Product productFactory();
class Factory {
  static Map<Type, productFactory> factory = new Map();
}
So I give that a try in my Hipster MVC (abstract creator and product) and Dart Comics (concrete creator and product) codebases. In Hipster MVC, the HipsterCollection class had served as the abstract creator, creating HipsterModel objects from attributes fetched via a RESTful backend. Last night it got the map-of-factories treatment. Tonight, I move that out into a separate Factory class:
typedef HipsterModel modelMaker(Map attrs);

class Factory {
  static Map factory = new Map();
}
This works just fine. HipsterCollection is now capable of creating concrete instances of HipsterModel (e.g. ComicBook from Dart Comics) using this Factory:
class HipsterCollection extends IterableBase {
  // ...
  HipsterModel modelMaker(attrs) => Factory.factory[this.runtimeType](attrs);
  // ...
}
But you know what? Factory.factory looks ugly. It would be much cooler if that were just:
  HipsterModel modelMaker(attrs) => Factory[this.runtimeType](attrs);
Unfortunately operators, like the square bracket lookup operator, cannot be static:
class _Factory {
  static Map<Type, modelMaker> factory = new Map();

  // This won't work!!!
  static operator [](type) => factory[type];
}
I know this does not work because I try it out only to get a nice little exception:
Internal error: 'package:hipster_mvc/hipster_factory.dart': error: line 11 pos 19: operator overloading functions cannot be static
  static operator [](type) => factory[type];
Bah!

All is not lost… as long as I am willing to resort to some compile time constant chicanery. And of course I am more than willing. I rename the Factory class as _Factory and declare a compile time constant of Factory which of type _Factory:
_Factory Factory = const _Factory();

class _Factory {
  static Map<Type, modelMaker> factory = new Map();

  const _Factory();
  operator [](type) => factory[type];
  operator []=(type, function) { factory[type] = function; }
}
Which does the trick, though it feels a little dirty. Dirty because I declare a compile-time constant whose sole purpose is to store changing key-value pairs. But it works because the instance variable factory fails on the _Factory instance which leaves Dart to fall back on static variable lookup.

And it is at this point that I realize that, yes, I am an idiot.

Because I have gone to all of this effort even though an empty HashMap is also a compile time constant. So the _Factory class and Factory compile time constant can be replaced with:
typedef HipsterModel modelMaker(Map attrs);
Map<Type, modelMaker> Factory = {};
Well, that was a long way to go to wind up with a simple HashMap lookup.

Ah well, maybe that const + static method trick will come in handy some day. Stranger things have happened.

But for today, I have my map-of-factories in better shape with an extremely small amount of code. Demonstrating publicly that I'm a dummy is a small price to pay for that!

Tomorrow: benchmarking.



Day #104

No comments:

Post a Comment