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 MapThis works just fine.factory = new Map(); }
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