Thursday, November 19, 2015

Creating and Reading Dart Annotations in Flyweights


I am settling comfortably into Dart mirrors as a solution for obtaining concrete instances in the Flyweight pattern. They are not a one-size-fits-all solution, but they strike a nice balance between usability in the client context (especially when paired with Dart's factory constructors) and maintainability of the flyweight code. That said, I do not want to leave reasonable options unexplored.

While reasonable people can disagree on what reasonable options are, I felt the siren's song of code annotation more than once while working with this pattern. So today, I give into that temptation. Hopefully I won't be dashed on rocks for my trouble.

While working other angles on the problem of concrete classes, annotations were appealing mostly because of their constant nature. My current CoffeeFlavor interface is a regular (non-constant) class:
class CoffeeFlavor {
  // Lots of static, mirror code from last night here...
  // Factory constructor:
  factory CoffeeFlavor(name) {
    return _cache.putIfAbsent(name, () =>
        classMirrors[new Symbol(name)].
            newInstance(new Symbol(''), []).
            reflectee
    );
  }
  // Normal class stuff:
  String get name => "Fake Coffee";
  double get profitPerOunce => 0.0;
}
Concrete implementations that get used as flyweights are similarly non-constant:
class Cappuccino implements CoffeeFlavor {
  String get name => 'Cappuccino';
  double get profitPerOunce => 0.35;
}
Due to the factory constructor, various solutions to the flyweight caching felt like a constant was the only solution. In the end, I could not make them work — hence last night's mirror solution.

Somewhere in my brain I thought that, since annotations are constants, maybe they can help here. But now that I am faced with the blank page, all I can think to do is mark a concrete class with an annotation to help finding and caching it:
@flavor
class Cappuccino implements CoffeeFlavor {
  String get name => 'Cappuccino';
  double get profitPerOunce => 0.35;
}
That seems to offer nothing new over yesterday's solution which made use of the implemented interface to find concrete classes. If anything, the annotation is one extra line.

Still, while I am here, it might be fun to write some Dart code that can read and apply annotations. I start by defining the class and annotation const:
// Annotation Class
class Flavor { const Flavor(); }
// Annotation instance
const flavor = const Flavor();
Yesterday's code found concrete classMirrors by filtering for “super interfaces” that contained CoffeeFlavor:
  static Map classMirrors = _allDeclarations.
    keys.
    where((k) => _allDeclarations[k] is ClassMirror).
    where((k) =>
      _allDeclarations[k].superinterfaces.contains(reflectClass(CoffeeFlavor))
    ).
    fold({}, (memo, k) => memo..[k]= _allDeclarations[k]);
Tonight, I need to similarly work though all class declarations, but filter on metadata (annotations):
  static Map classMirrors = _allDeclarations.
    keys.
    where((k) => _allDeclarations[k] is ClassMirror).
    where((k) =>
      _allDeclarations[k].metadata.map((m)=> m.type.reflectedType).contains(Flavor)
    ).
    fold({}, (memo, k) => memo..[k]= _allDeclarations[k]);
The _allDeclarations.keys is a map of symbols (e.g. #Cappuccino) pointing to declaration mirrors. So iterating over all the keys, then filtering on _allDeclarations[k] gives me individual declarations like the concrete class definition. From there, I map the annotations/metadata to the corresponding annotation type. In this case I want the Flavor annotation so that any classes with the Flavor annotation are returned.

If I had declared Flavor with properties and set them in the const declaration, then I could get those properties with the getField() method. Here, however, I just need to know if the @flavor annotation is being used. So I am already done!

My code continues to pass dartanalyzer and the coffee shop code that relies on this flyweight code similarly continues to work. Annotations do not buy me anything substantive in this case (at least that I can see). Still it is good to know how to extract and apply them.

(Play with this code on DartPad: https://dartpad.dartlang.org/c7dabc0c57a93e8d88d7)


Day #8

No comments:

Post a Comment