I have to admit it. This took me a little while to figure out.
Last night I was able to apply the
@MirrorsUsed
annotation to some mirror-based Dart code, thus reducing the size of dart2js
compiled code from 1.6MB to 170kb. The solution worked, but was not practical for the particular use I am exploring. So tonight, I figured out how to get @MirrorsUsed
to do a little more.The code in question is a Factory Method pattern implementation. I already have a couple of other approaches as well, but being a goofball for dynamic language stuff, I cannot resist mirrors, even if they are terribly slow. But I would prefer my code not be slow and incredibly large, which is where
@MirrorsUsed
comes into the picture.Last night's approach specified the list of all classes that my code reflects upon. Since this is sample code, I only reflect on one class,
ConcreteProduct
(a name describing the classes role in the pattern, not an actual implementation). Since there is only one class that gets reflected, the @MirrorsUsed
annotation looks like:@MirrorsUsed(targets: 'ConcreteProduct') import 'dart:mirrors';Easy-peasy.
Except it will not work in the typical Factory Method pattern case of frameworks. In a framework, the base classes are in one package and libraries are usually in a separate packages. My base
Creator
class (that would reside the in framework package) is the one that does the reflecting:abstract class Creator { Type productClass; Product productMaker() { return reflectClass(productClass). newInstance(const Symbol(''), []). reflectee; } }The concrete creator class that defines the
productClass
instance variable and the concrete product class would both reside in a separate package:class ConcreteCreator extends Creator { Type productClass = ConcreteProduct; } class ConcreteProduct extends Product {}The problem is that the import occurs in the “framework” package, which means that the
@MirrorsUsed
annotation would have no way to know which class the concrete application classes will specific for reflection.This turns out the be the province of the
metaTargets
option to @MirrorsUsed
. The targets
option points to a list of classes that will be reflected. The metaTargets
, on the other hand, are a list of code annotations that will identify classes that receive reflection.This means that I have to write my own annotations. So I define a
@product
annotation as:const FactoryProduct product = const FactoryProduct(); class FactoryProduct { const FactoryProduct(); }I then tell the dart:mirrors package that it needs to reflect classes annotated with
@product
with the metaTargets
option:@MirrorsUsed(metaTargets: const[FactoryProduct]) import 'dart:mirrors';Lastly, I annotate my concrete (application) class that will be reflected:
@product class ConcreteProduct extends Product {}With that, I can compile (relatively) small JavaScript:
$ dart2js -o tool/benchmark.dart.js tool/benchmark.dart $ ls -lh tool/benchmark.dart.js -rw-r--r-- 1 chris chris 171K Jul 2 23:03 tool/benchmark.dart.jsAnd my JavaScript benchmarking code still runs under Node:
$ node tool/benchmark.dart.js Factory Method — Subclass(RunTime): 0.09583097409453985 us. Factory Method — Map of Factories(RunTime): 4.2090907942975235 us. Factory Method — Mirrors(RunTime): 7.62668883490888 us.In the end, it was not too tricky to get this working. It just took me a while to realize that annotations were the order of the day.
That will do for my initial investigation into the Factory Method for Design Patterns in Dart. Up tomorrow, I switch to a behavioral Design Pattern that I have always hated.
Day #110
"I switch to a behavioral Design Pattern that I have always hated."
ReplyDeleteYou mean the spaghetti pattern :-P