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