I am reasonably happy with last night's pluggable adapter pattern in Dart. But there are always nagging concerns.
My current solution builds a large registry of methods and arguments, organized by type. In this case, my pluggable adapters support a universal remote control for robots, so the registry is organized by robot class:
static Map<Type, Map> _registry = {
Robot: {
'forward': [#move, [Direction.NORTH]],
'backward': [#move, [Direction.SOUTH]],
'left': [#move, [Direction.WEST]],
'right': [#move, [Direction.EAST]]
},
Bot: {
'forward': [#goForward, []],
'backward': [#goBackward, []],
'left': [#goLeft, []],
'right': [#goRight, []]
}
};
I am a little concerned that this would grow past manageable state once the universal remote control supports more than a dozen robots or so. I also imagine that I will wind up adding conditionals for more than one type of robot in the various movement methods that I am adapting. Currently, they are a relatively clean mirror-based implementation: void moveForward() {
var _ = _registry[_robot.runtimeType]['forward'];
reflect(_robot).invoke(_[0], _[1]);
}
But, like I said, I can see that getting messy after a while.So instead, I try moving the adapted methods back into individual classes, one for each kind of robot. So the
Robot
adapter, which its move()
method and direction arguments will look like:class RobotAdapterToUbot implements Ubot { Robot _robot; RobotAdapterToUbot(this._robot); String get xyLocation => _robot.location; void moveForward() { _robot.move(Direction.NORTH); } // other move methods here... }Similarly, the adapter for
Bot
, which sports go-* methods for movements will look like:class BotAdapterToUbot implements Ubot { Bot _bot; BotAdapterToUbot(this._bot); String get xyLocation => "${_bot.x}, ${_bot.y}"; void moveForward() { _bot.goForward(); } // other move methods here... }I like this solution for cases in which there is variation in adaptee interfaces. For example, the
Bot
adapter supports a specialized xyLocation
getter without having to perform a type check in a single adapter class. These adapters need to support the same interface,
Ubot
in this case, so that a delegating class can invoke the same method regardless of adaptee. The delegating class will provide the object that gets used in client code. To start with, I make that delegating class implement the same Ubot
interface: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); } String get xyLocation => _ubot.xyLocation; void moveForward() { _ubot.moveForward(); } // other move methods here... }This class sets the delegated adapter in the constructor based on the adaptee type. If
UniversalRemoteRobot
is constructed for a Robot
, then the RobotAdapterToUbot
is used. For a Bot
, the delegated adapter is a BotAdapterToUbot
. As long as new robot types are assigned adapters to the Ubot
interface, it should be easy to add them—they just need to be delegated in the constructor.This is, I believe, the exact structure of the second pluggable adapter from the Gang of Four book, though their example had the delegating class and the adapters supporting different interfaces. I may try something like that tomorrow. For now, it does not get much easier than having the
moveForward()
in the delegator invoke the same method in the delegated adapter.With that, my universal remote control works exactly the same for
Bot
and Robot
instances: var r = new Bot();
// This works exactly the same:
// var r = new Robot();
var universalRobot = new UniversalRemoteRobot(r);
print("Start moving the robot.");
universalRobot
..moveForward()
..moveForward()
..moveForward()
..moveForward()
..moveForward();
print("The robot is now at: ${universalRobot.xyLocation}.");
I still think last night's mirror-based approach has its place—especially in situations where I am assured of simple method invocations on the adaptees. But this delegated pluggable adapter approach feels like a better all-purpose solution.I will likely put that sentiment to the test. Tomorrow.
Play with the code on DartPad: https://dartpad.dartlang.org/2c87242d40969a9a3c2c.
Day #52
No comments:
Post a Comment