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 MapIf I want to clear that cache after all_drawingApiCache = {}; Shape(Type drawingApi) { _drawingApi = _drawingApiCache.putIfAbsent( drawingApi, ()=> reflectClass(drawingApi).newInstance(new Symbol(''), []).reflectee ); } // ... }
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