Wednesday, July 23, 2014

Varying Only the Import in Dart


Tonight, I explore a new kind of Dart refactoring: varying the import statement.

This occurs in the benchmarking code for the Visitor Pattern as part of my research for the future Design Patterns in Dart. I have three different approaches that I would to compare. After refactoring and refactoring and refactoring, I am finally down to very slim scripts that only vary by the implementation code being imported (and the name of the benchmark):
$ diff -u tool/benchmark.dart tool/benchmark_single_dispatch_iteration.dart
--- tool/benchmark.dart 2014-07-23 23:11:55.933361972 -0400
+++ tool/benchmark_single_dispatch_iteration.dart       2014-07-23 23:12:45.545363182 -0400
@@ -2,12 +2,12 @@
 
 import 'package:dpid_benchmarking/pattern_benchmark.dart';
 
-import 'package:visitor_code/visitor.dart';
+import 'package:visitor_code/alt/single_dispatch_iteration/visitor.dart';
 
 main(List<String> args) {
   BenchmarkRunner.main(
     args,
-    "Classic Visitor Pattern",
+    "Nodes iterate w/ single dispatch",
     _setup,
     _run
   );
The BenchmarkRunner.main() method signature is a little ugly, but aside from the minor quibble I feel pretty good about this. Except…

There are two dozen lines of code that follow this that are exactly identical in each of the three benchmark scripts. The setup and the actual code that is executed is 100% duplicate between the three files. It looks something like:
var visitor, nodes;

_setup() {
  visitor = new PricingVisitor();

  nodes = new InventoryCollection([mobile(), tablet(), laptop()]);
  // Add more sub-nodes to complexify the structure...
}

_run(loopSize) {
  // nodes used
  // visitor used
  // (nodes accept visitor a bunch of times)
}
I am creating a closure over visitor and nodes so that they can be shared between the setup and benchmark runner when executed by BenchmarkRunner.

What is important to me in this case is that the PricingVisitor and InventoryCollection classes have the same name, but are defined differently by the three different imported packages in the three different scripts that I have.

This is almost certainly unique to benchmarking considerations, but still, how can I move this duplicated code out into a single file that can be shared? Dart parts will not work because the file containing the common setup and run code would have to be the main file and only one of the three implementations could be part of it. Conditional imports do not work in Dart.

Unfortunately, I am more or less stumped on a good way to do this. Dart is not meant to used like this (I'm not sure any language is). That said, I can come up with something that works. I have to use the Factory Method pattern to create the visitor and the node structure (I also have to pull in makers for the nodes within the structure). In the end, the overhead does not seem to save much in the way of lines of code:
import '_setup.dart' as Setup;

main(List<String> args) {
  Setup.visitorMaker = ()=> new PricingVisitor();
  Setup.inventoryCollectionMaker = (items) => new InventoryCollection(items);
  Setup.mobile = mobile;
  Setup.tablet = tablet;
  Setup.laptop = laptop;
  Setup.app = app;

  BenchmarkRunner.main(
    args,
    "Classic Visitor Pattern",
    Setup.setup,
    Setup.run
  );
}
What I do gain is the ability to keep the test setup and running logic in one place: _setup.dart. And in there, I simply define top-level variables to support the Setup.XXX setters:
library visitor_setup;

var visitorMaker;
var inventoryCollectionMaker;
var mobile;
var tablet;
var laptop;
var app;

var visitor, nodes;

setup() {
  visitor = visitorMaker();
  // ...
}
// ...
I am not satisfied with that, but it does have the advantage of keeping the common logic in one place. At the risk of rationalizing this too much, I note that the 6 Setup assignments are only needed because I am using 4 node types to intentionally create complexity in the data structure being tested.

I will leave this as “good enough” for the time being. Using this on other design patterns will ultimately decide if this approach is usable. So I will pick back up with that tomorrow.



Day #131

No comments:

Post a Comment