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