Thursday, January 7, 2016

Delegating Mirrors Might Make Me Cry


There is a point at which programmers should leave well enough alone. That point and I have long quarrels typically ending with me saying something that I regret and then driving around for hours until I have cooled down sufficiently to apologize.

This is likely to be one of those situations.

After last night, my delegating, pluggable adapter is beautiful in Dart:
class UniversalRemoteRobot implements Ubot {
  var robot;
  UniversalRemoteRobot(this.robot);

  Ubot get _ubot {
    if (robot is Robot) return new RobotAdapterToUbot(robot);
    if (robot is Bot)   return new BotAdapterToUbot(robot);
    return new NullAdapterToUbot(robot);
  }

   dynamic noSuchMethod(i) => reflect(_ubot).delegate(i);
}
The constructor for the UniversalRemoteRobot class accepts an instance of some kind of robot and assigns it to the this.robot instance variable. The noSuchMethodMethod() method, which is available to any Dart object, delegates any unknown methods called on UniversalRemoteRobot objects to the appropriate adapter, as determined by the _ubot getter.

The delegate() method, which comes from the dart:mirrors library, makes maintenance of this code quite nice. The Ubot interface currently support four movement methods as well as a position getter:
abstract class Ubot {
  String get xyLocation;
  void moveForward();
  void moveBackward();
  void moveLeft();
  void moveRight();
}
Were that to change—if additional directions were added or cameras were supported—I would have to do nothing to my pluggable adapter. The delegate() would continue to merrily send those new actions to the appropriate adapter. I should be contented by this.

But...

Whenever a new kind of robot is supported, I need to define the appropriate Ubot adapter and remember to add the adapter to the _ubot delegating getter. That is not completely horrible, which is why I ought to leave this alone.

That said, I can rationalize a concern for the size of the getter. Right now, I only support robots from two vendors in my universal remote control for robots code. What happens when I have dozens of robot vendors to support? A hundred line getter won't be too pretty then. Worse, it will obscure the main focus of this class, which is delegating to pluggable adapters.

So I would like to dynamically choose the adapter. This is going to involve more mirrors, so I start by switching the _ubot getter to return a mirror instead of a regular instance:
class UniversalRemoteRobot implements Ubot {
  var robot;
  UniversalRemoteRobot(this.robot);

  InstanceMirror get _ubot {
    if (robot is Robot) return reflect(new RobotAdapterToUbot(robot));
    if (robot is Bot)   return reflect(new BotAdapterToUbot(robot));
    return reflect(new NullAdapterToUbot(robot));
  }

   dynamic noSuchMethod(i) => _ubot.delegate(i);
}
This also allows the noSuchMethod() declaration to use the getter right away, instead of having to reflect first.

Now for the fun stuff. To find the adapter class, I need a mirror on the current library. I cannot just say give-me-the-current-library, I have to reflect on an object in the current library (like this), find the class mirror via the type property, then ask for the owner (the library):
    LibraryMirror thisLibrary = reflect(this).type.owner;
I can already feel a good cry coming on.

I have been following the convention of naming the adapter classes <Adaptee Name>AdapterToUbot, so the adapter for the Robot class becomes RobotAdapterToUbot. Using that convention, I can determine the adapter class as:
    Symbol adapterClassName = new Symbol('${robot.runtimeType}AdapterToUbot');
That's not too horrible. This next bit is.

To find the class mirror for the adapter, I have to look it up in the map of all declarations in the current library:
    var adapterClass = thisLibrary.declarations[adapterClassName];
With that class mirror for the adapter, I can get a new instance mirror with the newInstance() method:
    Symbol constructor = new Symbol('');
    return adapterClass.newInstance(constructor, [robot]);
The empty constructor name is required so that newInstance() can also work with named constructors.

Putting it all together, the delegating adapter getter now looks like:
class UniversalRemoteRobot implements Ubot {
  // ...
  InstanceMirror get _ubot {
    LibraryMirror thisLibrary = reflect(this).type.owner;

    Symbol adapterClassName = new Symbol('${robot.runtimeType}AdapterToUbot');
    var adapterClass = thisLibrary.declarations[adapterClassName];

    Symbol constructor = new Symbol('');
    return adapterClass.newInstance(constructor, [robot]);
  }
  // ...
}
That works in the sense that the code runs and does what it is supposed to do. On some level, it also makes the code more maintainable, since I now only have to add a new <Adaptee Name>AdapterToUbot declaration. But this utterly fails to make the code more readable. My beautiful UniversalRemoteRobot class is now a mirrored mess. I do not want to even think how prefixed libraries might affect all of this.

I may leave this exploration path alone. It seems possible. I can clean it up some by moving this code out into an explicit delegator class. But I doubt that I can clean it up to the point that I can include it Design Patterns in Dart without confusing actual pattern discussion.

So if you'll excuse me now, I need to drive around weeping for a while.

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



Day #57

No comments:

Post a Comment