Wednesday, January 6, 2016

Dart Delegation


Nearly done exploring the adapter pattern, I would like to take one more look at delegation in Dart. Delegation has come in handy the past few days as I have explored pluggable adapters, but it has been informal delegation. I am curious if there are more formal approaches available.

The delegation that I have been using in in the constructor of my "universal remote robot" class:
class UniversalRemoteRobot implements Ubot {
  Ubot _ubot;
  UniversalRemoteRobot(robot) {
    if (robot is Robot) _ubot = new RobotAdapterToUbot(robot);
    else if (robot is Bot) _ubot = new BotAdapterToUbot(robot);
    else _ubot = new NullAdapterToUbot(robot);
  }
  // ...
}
The _ubot private instance variable holds the delegated robot instance. Since this has been exploring adapters, the delegate points to the appropriate class that adapts a robot to the interface used in this universal remote control packages.

I might clean up that constructor by moving it into a getter:
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);
  }
  // ...
That does eliminate an else if, which I always appreciate. Still, it is just a superficial change and does not really touch on delegation.

What I am most curious about is what to do with calling methods on the delegated instance:
class UniversalRemoteRobot implements Ubot {
  // ...
  String get xyLocation => _ubot.xyLocation;

  void moveForward()  { _ubot.moveForward(); }
  void moveBackward() { _ubot.moveBackward(); }
  void moveLeft()     { _ubot.moveLeft(); }
  void moveRight()    { _ubot.moveRight(); }
}
There seems to be some repetition there. It seems like it might be time for some mirror fun.

I import dart:mirrors, then delete the four move* methods. In there place, I declare an InstanceMirror getter and a noSuchMethod() method:
import 'dart:mirrors';

class UniversalRemoteRobot implements Ubot {
  // ...

  InstanceMirror get _botMirror => reflect(_ubot);

  dynamic noSuchMethod(i) {
    return _botMirror.invoke(i.memberName, []).reflectee;
  }

  String get xyLocation => _ubot.xyLocation;
}
The noSuchMethod() is available on all Dart objects. It receives an invocation mirror instance describing the arguments that were passed to non-existent method. In this case, I know that my four methods do not accept arguments, so I can ignore the list of arguments. I do need to know the specific movement method that was called, which is stored in memberName of the invocation mirror.

The last piece of the puzzle there is a mirror of the _ubot delegated adapter. The only way to call an arbitrary method on an object in Dart is through a mirror. So the _botMirror getter returns that mirror from the reflect() function in dart:mirrors.

With that, I have an instance mirror of my adapter, and can invoke whichever movement method was sent to the UniversalRemoteRobot instance:
  universalRobot = new UniversalRemoteRobot(robot);
  print("Start moving the robot.");
  universalRobot
    ..moveForward()
    ..moveForward()
    ..moveForward()
    ..moveForward()
    ..moveForward();
  print("The robot is now at: ${universalRobot.xyLocation}.");
And that works just fine, thank you:
$ ./bin/play_robot.dart
Start moving the robot.
  I am moving Direction.NORTH
  I am moving Direction.NORTH
  I am moving Direction.NORTH
  I am moving Direction.NORTH
  I am moving Direction.NORTH
The robot is now at: 0, 5.
I still need to handle that xyLocation getter method that is sent to the delegate. This means a conditional in the noSuchMethod() declaration to handle getters:
class UniversalRemoteRobot implements Ubot {
  // ....
  dynamic noSuchMethod(i) {
    if (i.isGetter) return _botMirror.getField(i.memberName).reflectee;
    return _botMirror.invoke(i.memberName, []).reflectee;
  }
}
With that, I have completely delegated the pluggable adapter methods from the delegator to appropriate adapater. That is nice, especially since it is only four lines of code, but... that is a dense 4 lines of code. Say, while I was looking through the Dart InstanceMirror documentation, I could not help but notice that there is a delegate() method. That could not possibly do exactly what I expect it to, could it?

In fact, it does just that:
class UniversalRemoteRobot implements Ubot {
  // ...
  dynamic noSuchMethod(i) => reflect(_ubot).delegate(i);
}
That is lovely. Simply lovely.

With that, I can write the entire delegator class in my pluggable adapter pattern as:
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);
}
Making this even nicer is that, no matter what methods are added to the Ubot interface, this does not need to change in order to support it. I do not have to define new methods. I do not need any additional conditionals in noSuchMethod(). I like this very much.

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


Day #56

No comments:

Post a Comment