Tonight, I continue my exploration of the Reflectable package as applied to the Flyweight Pattern.
I am quite happy with the current state of the Reflectable-based code. I have defined a
flavor
constant that inherits from Reflectable
so that it can annotate my code:class Flavor extends Reflectable { const Flavor() : super(newInstanceCapability); } const flavor = const Flavor();I then use this constant to mark the individual concrete flyweight objects—in the case of this example, coffee flavors for my coffee shop:
@flavor class Cappuccino implements CoffeeFlavor { String get name => 'Cappuccino'; double get profitPerOunce => 0.35; } @flavor class Espresso implements CoffeeFlavor { String get name => 'Espresso'; double get profitPerOunce => 0.15; }It seems a bit of a nuisance to have to annotate every concrete flyweight class like this. Thanks to a pointer from Erik Ernst in yesterday's comments, I can eliminate that nuisance.
Reflectable works by only applying a limited subset of built-in
dart:mirrors
capabilities. Currently, I only use newInstanceCapability so that the CoffeeFlavor
factory constructor can create new instances of the concrete flyweight objects:class CoffeeFlavor { // ... factory CoffeeFlavor(name) { return _cache.putIfAbsent(name, () => classMirrors[name].newInstance('', [])); } // ... }Reflectable objects like my
flavor
constant come with the built-in ability to find all classes annotated by the object, so the classMirrors
map is easily assembled from flavor
:class CoffeeFlavor { // ... static Map classMirrors = flavor. annotatedClasses. fold({}, (memo, c) => memo..[c.simpleName]= c); factory CoffeeFlavor(name) { /* ... */ } // ... }Getting back to Erik's suggestion, I can add a new capability to my reflectable
Flavor
class—the subtypeQuantifyCapability:class Flavor extends Reflectable { const Flavor() : super(newInstanceCapability, subtypeQuantifyCapability); } const flavor = const Flavor();What this means is that I can move all of the
@flavor
annotations from the concrete flyweight CoffeeFlavor
classes and instead place one on the CoffeeFlavor
class itself:@flavor class CoffeeFlavor { // ... static Map classMirrors = flavor. annotatedClasses. fold({}, (memo, c) => memo..[c.simpleName]= c); factory CoffeeFlavor(name) { /* ... */ } // ... } class Cappuccino implements CoffeeFlavor { String get name => 'Cappuccino'; double get profitPerOunce => 0.35; } class Espresso implements CoffeeFlavor { String get name => 'Espresso'; double get profitPerOunce => 0.15; } // More concrete flyweight classes here...With that, the
annotatedClasses
includes every class that extends or implements the annotated CoffeeFlavor
class. I have the same exact code and functionality, but with a single annotation instead of one annotation per concrete class. Furthermore, this works across libraries. If I define
CoffeeFlavor
and the various concrete classes in a library, but define another concrete class in the main entry point:// The library with the factory and flyweights: import 'package:flyweight_code/coffee_shop.dart'; // Concrete flyweight outside the library class Mochachino implements CoffeeFlavor { String get name => "Mochachino"; double get profitPerOunce => 0.3; } main() { // Go about coffee shop business here... }It still works. That is, the
flavor
constant is still aware of this Mochachino
class as a "sub type".I had thought to originally try to bend Reflectable to the point that it could mimic my original implementation. In there, I use
dart:mirrors
to search through all code to find classes that implement CoffeeFlavor
. That seemed the most explicit implementation of what I needed—a list of available concrete classes without much overhead. But I have to admit that Reflectable with
subtypeQuantifyCapability
accomplishes exactly the same thing with almost no additional overhead—just a constant or two. The resulting code is easier to read and the generated JavaScript (as I found last night) is much smaller. This is a win all around.Day #13
No comments:
Post a Comment