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