Oh, what the hell. I finished yesterday hand-waving over a particular implementation. Let's see if I can actually implement a reference counting handle body in Dart. There may be no practical application for this in a garbage collected language like Dart, but we'll see...
I continue to use
Shape
as the abstraction in my bridge pattern exploration. It will now serve double duty as the handle class, holding references to the body implementation. Well, it already did that (which is rather the point of the pattern), but now it needs to reference, dereference, and delete implementors as well.The implementor is
DrawingApi
, which knows that subclasses will, among other things, know how to draw a circle:abstract class DrawingApi { void drawCircle(double x, double y, double radius); }To maintain reference counts,
DrawingApi
needs a _refCount
instance variable along with ref()
and deref()
methods to increase and decrease that counter:abstract class DrawingApi { int _refCount = 0; void ref() { _refCount++; print(' $this Increased refcount to $_refCount'); } void deref() { _refCount--; print(' $this Decreased refcount to $_refCount'); } void drawCircle(double x, double y, double radius); }In client code, I can create an instance of two subclasses of
DrawingApi
:main() { var api1 = new DrawingApi1(), api2 = new DrawingApi2(); // ... }Then I initially set those as the "drawer" (the thing that draws, not a thing that holds stuff in desks) property for
Circle
instances:main() { // ... var circle1 = new Circle(1.0, 2.0, 3.0)..drawer = api1, circle2 = new Circle(0.0, 6.0, 1.0)..drawer = api1, circle3 = new Circle(2.0, 2.0, 1.5)..drawer = api2; // ... }The
Circle
class is a concrete implementor of the Shape
abstraction / handle class. So assigning api1
to two difference Circle
instances needs to update the number of references to api1
accordingly.I more or less copy the code from the handle / body example from the bridge pattern chapter in the Gang of Four book. The
drawer()
setter is responsible for noting the new reference, and noting the derefence for the existing object. If the reference count goes to zero, the handle class needs to delete the reference, which I do by assigning the _drawingApi
instance variable to null
:abstract class Shape { DrawingApi _drawingApi; set drawer(DrawingApi other) { other.ref(); if (_drawingApi != null) { _drawingApi.deref(); if (_drawingApi._refCount == 0) { print(' ** Deleting no longer used $_drawingApi **'); _drawingApi = null; } } _drawingApi = other; } }It is here that I finally realize that there is no point to doing any of this—at least not in this example. Even without explicitly setting
_drawingApi
to null
, it gets assigned to a different value on the following line. Once that happens, Dart itself notes that there is one fewer references to the object and, if zero, schedules the object for garbage collection.I don't know why I thought this might be necessary in some cases. I do get easily confused in the presence of C++.
Anyhow, back in my client code, I draw those circles with the mixture of
api1
and api2
, then update the drawers so that everyone is using api2
:main() { // ... circle1.draw(); circle2.draw(); circle3.draw(); circle1.drawer = api2; circle2.drawer = api2; api1 = api2 = null; circle1.draw(); circle2.draw(); circle3.draw(); }When I run this code, I get what I expect. The first time through a mixture of
api1
and api2
drawers are used, when I switch to all-api2
, the deletion of the api2
reference is noted, then api2
drawing commences:./bin/draw.dart Instance of 'DrawingApi1' Increased refcount to 1 Instance of 'DrawingApi1' Increased refcount to 2 Instance of 'DrawingApi2' Increased refcount to 1 [DrawingApi1] circle at (1.0, 2.0) with radius 3.000 [DrawingApi1] circle at (0.0, 6.0) with radius 1.000 [DrawingApi2] circle at (2.0, 2.0) with radius 1.500 Instance of 'DrawingApi2' Increased refcount to 2 Instance of 'DrawingApi1' Decreased refcount to 1 Instance of 'DrawingApi2' Increased refcount to 3 Instance of 'DrawingApi1' Decreased refcount to 0 ** Deleting no longer used Instance of 'DrawingApi1' ** [DrawingApi2] circle at (1.0, 2.0) with radius 3.000 [DrawingApi2] circle at (0.0, 6.0) with radius 1.000 [DrawingApi2] circle at (2.0, 2.0) with radius 1.500So it works, but Dart already had me covered with garbage collection. The effort is not a complete waste. I did clear up whatever C++ confusion I was suffering. Potentially more important, I have client code that can demonstrate sharing implementors such that garbage collection will reclaim them when they go unused.
Play with the code on DartPad: https://dartpad.dartlang.org/43585b1f6e0f1403fc5a.
Day #79
No comments:
Post a Comment