Thursday, January 28, 2016

Sharing Implementors is Easy in the Bridge Pattern


Reference counting is something of a lost art. I, for one, hope it stays that way. If there was a way to mess up counting, I was more than up to the task of finding it. That's only a slight exaggeration.

So I very much appreciate garbage collected languages like Dart. Of course, garbage collection can only do so much—I promise you that it is still possible to build web applications that consume insane amounts of memory. In that vein, tonight I investigate sharing implementors in the bridge pattern.

The Gang of Four book describes Handle Body idiom for tackling this in C++. But that's for my old nemesis reference counting. In Dart, I have to think factory constructor singletons would be an easy way to tackle this. For example, consider the wildly memory intensive DrawingApi1 class:
class DrawingApi1 implements DrawingApi {
  void drawCircle(double x, double y, double radius) {
    print(
      "[DrawingApi1] "
      "circle at ($x, $y) with "
      "radius ${radius.toStringAsFixed(3)}"
    );
  }
}
Clearly, I do not want that class to instantiate new objects each time a Circle refined abstraction is created:
  List<Shape> shapes = [
    new Circle(1.0, 2.0, 3.0, new DrawingApi1()),
    new Circle(0.0, 6.0, 1.0, new DrawingApi1()),
    new Circle(2.0, 2.0, 1.5, new DrawingApi1()),
    new Circle(5.0, 7.0, 11.0, new DrawingApi2()),
    new Circle(1.0, 2.0, 3.0, new DrawingApi1()),
    new Circle(5.0, -7.0, 1.0, new DrawingApi2()),
    new Circle(-1.0, -2.0, 5.0, new DrawingApi1())
  ];
Even if DrawingApi2 is significantly more lightweight than DrawingApi1, enough of the latter will drag the browser / system to a halt. But a simple singleton factory constructor solves that neatly:
class DrawingApi1 implements DrawingApi {
  static final DrawingApi1 _drawingApi = new DrawingApi1._internal();
  factory DrawingApi1()=> _drawingApi;
  DrawingApi1._internal();

  void drawCircle(double x, double y, double radius) { /* ... */ }
}
The class variable _drawingApi is constructed once from a private constructor. The regular DrawingApi1() is a factory constructor that only returns that one _drawingApi reference. And the internal constructor is a simple, no-argument private constructor.

The only case that does not cover is when the number of references to a DrawingApi1 instance goes to zero. The start-at-zero case is handled automatically for me by Dart. The private _drawingApi class variable is not assigned until the constructor is invoked for the first time—that is just thanks to Dart's lazy instantiation. So no memory will be used unless and until the first DrawingApi1 shape is drawn.

If I really needed to reclaim memory, I might try a handle-body kind of thing. But I'd probably get it wrong. So instead I use mirrors (because mirrors are always easier than reference counting). If I switch to constructing shapes with the class name instead of an instance, the individual shapes might look like:
  List<Shape> shapes = [
    new Circle(1.0, 2.0, 3.0, DrawingApi1),
    new Circle(0.0, 6.0, 1.0, DrawingApi1),
    new Circle(2.0, 2.0, 1.5, DrawingApi1),
    new Circle(5.0, 7.0, 11.0, DrawingApi2),
    new Circle(1.0, 2.0, 3.0, DrawingApi1),
    new Circle(5.0, -7.0, 1.0, DrawingApi2),
    new Circle(-1.0, -2.0, 5.0, DrawingApi1)
  ];
The Circle constructor still redirects the last parameter, which is now a Type, to the abstraction superclass:
class Circle extends Shape {
  double _x, _y, _radius;
  Circle(this._x, this._y, this._radius, Type drawingApi) :
    super(drawingApi);
  // ...
}
Finally, the constructor in the Shape abstraction can putIfAbsent() on the cache:
import 'dart:mirrors' show reflectClass;
abstract class Shape {
  DrawingApi _drawingApi;
  static Map _drawingApiCache = {};

  Shape(Type drawingApi) {
    _drawingApi = _drawingApiCache.putIfAbsent(
      drawingApi,
      ()=> reflectClass(drawingApi).newInstance(new Symbol(''), []).reflectee
    );
  }
  // ...
}
If I want to clear that cache after all Shape instances have been removed, I can use a simple clear():
abstract class Shape {
  // ...
  void reset() { _drawingApiCache.clear(); }
  // ...
}
The rest remains unchanged from previous nights. The bridge between abstraction (shape) and implementor (a drawing API) is used in the draw() method in the refined abstraction:
class Circle extends Shape {
  // ...
  void draw() {
    _drawingApi.drawCircle(_x, _y, _radius);
  }
  // ...
}
I draw two conclusions from this. First, I enjoy mirrors far more than is healthy. A handle-body class would have involved counting, but might have solved the last reference removal just as well (and possibly clearer). Second, singletons seem to solve the reference count for nearly all use-cases.


Play with the code on DartPad: https://dartpad.dartlang.org/befac6b199a2cebf1d76.

Day #78

No comments:

Post a Comment