Friday, January 1, 2016

What the Heck is a Pluggable Adapter?


Confession: I never understood a thing about pluggable adapters in the Gang of Four book. I have read that section on more than one occasion and not understood a damn word. So it seems like good grist for a blog post.

Part of my confusion is that I am not 100% clear on what is meant by the term. When first introduced in the book, a pluggable adapter describes "classes with built-in interface adaption." I am unclear on what "built-in" means and what gets it (the adapter or adaptee). Later, they describe a reusable widget that needs to work with similar objects even if they have different interfaces. I am going to focus on that second idea for now. Maybe I can figure out what they mean in the first definition for a follow-up post.

I am still working with my Robot Dart class that moves in different directions via a single move() method:
enum Direction { NORTH, SOUTH, EAST, WEST }

class Robot {
  int x=0, y=0;
  String get location => "$x, $y";

  void move(direction) {
    print("  I am moving $direction");
    switch (direction) {
      case Direction.NORTH:
        y++;
        break;
      case Direction.SOUTH:
        y--;
        break;
      case Direction.EAST:
        x++;
        break;
      case Direction.WEST:
        x--;
        break;
    }
  }
}
A competing robot manufacturer might have a different robot movement API that looks like:
class Bot {
  int x=0, y=0;
  void goForward()  { y++; }
  void goBackward() { y--; }
  void goLeft()     { x--; }
  void goRight()    { x++; }
}
So how do I go about defining an interface that can work with either of these? I think that if I can answer that, I will have a pluggable adapter.

The universal remote control that I am building requires a target interface that looks like:
abstract class Ubot {
  void moveForward();
  void moveBackward();
  void moveLeft();
  void moveRight();
}
When I previously only needed to support the Robot class, I could do so with a plain-old adapter like:
class UbotRobot implements Ubot {
  Robot _robot;
  UbotRobot(this._robot);

  void moveForward()  => _robot.move(Direction.NORTH);
  void moveBackward() => _robot.move(Direction.SOUTH);
  void moveLeft()     => _robot.move(Direction.WEST);
  void moveRight()    => _robot.move(Direction.EAST);
}
One way that I can convert that to support the Bot class as well is to add a conditional to each move-related method:
class UbotRobot {
  var _robot;
  UbotRobot(this._robot);

  bool get isRobot => _robot is Robot;
  bool get isBot   => _robot is Bot;

  void moveForward() {
    if (isRobot) _robot.move(Direction.NORTH);
    if (isBot) _robot.goForward();
  }
  // ...
}
That actually does the trick. In the client code, I can now use a Robot interchangeably with Bot when instantiating the universal UBot instance:
  var robot = new Bot(); // new Robot() also works
  var universalRobot = new UbotRobot(robot);
Regardless of which robot is used, the movement commands work, thanks to the adapter:
  print("Start moving the robot.");
  universalRobot
    ..moveForward()
    ..moveForward()
    ..moveForward()
    ..moveForward()
    ..moveForward();
  print("The robot is now at: ${universalRobot.location}.");
With the loquacious Robot, this results in:
$ ./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.
While the taciturn Bot produces:
$ ./bin/play_robot.dart                                                    
Start moving the robot.
The robot is now at: 0, 5.
Both kinds of robots are controlled by the same universal remote class.

It would be a pain to maintain an adapter like this. When the next robot is supported, I would have to make changes to each method. Worse, each method would eventually grow to contain one line for every supported interface. Better is to create a registry that contains enough information to invoke the appropriate commands on the adaptees. I think this must be what the Gang of Four were talking about with the three different versions of pluggable adapters.

I am unsure that I understand each of the types that they discuss. I do understand... mirrors! So I import Dart mirrors and declare a registry with sufficient information to issue move commands to each type:
import 'dart:mirrors';

class UbotRobot implements Ubot {
  var _robot;
  UbotRobot(this._robot);

  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, []]
    }
  };
  // ...
}
With that, I can reflect on the robot when invoking the appropriate movement method (with arguments):
class UbotRobot implements Ubot {
  // ...
  void moveForward() {
    var _ = _registry[_robot.runtimeType]['forward'];
    reflect(_robot).invoke(_[0], _[1]);
  }
  // ...
}
Happily, that works as desired. Don't believe me? Try the code on DartPad: https://dartpad.dartlang.org/09b8a2b1f6da874c7c68!

I think I have a idea of what pluggable adapters are now. I may explore the concept a little further tomorrow. Hopefully I can better understand those implementation discussions from the book now!


Day #51

No comments:

Post a Comment