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