Thursday, December 31, 2015

Adapting Procedural Code


So the basic adapter pattern in Dart is pretty darn boring. Case closed on research for the forthcoming Dart Design Patterns in Dart? Far from it. I have the utmost confidence in my ability to make things way more complicated than they might at first seem (or need to be).

Toward that end, tonight I try adapting procedural code to an a asynchronous interface. The code that currently powers the design patterns robot is quite procedural:
class Robot {
  // ...
  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;
    }
  }
}
I can easily move my robot forward a far as I like by sending multiple move() messages to the robot:
  var robot = new Robot();
  print("Start moving the robot.");
  robot
    ..move(Direction.NORTH)
    ..move(Direction.NORTH)
    ..move(Direction.NORTH)
    ..move(Direction.NORTH)
    ..move(Direction.NORTH);
  print("The robot is now at: ${robot.location}.");
With that, the robot winds up at 0, 5 (fives paces in the positive Y-direction):
$ ./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.
Unfortunately, this will not work with the Universal Robot Remote Control™. The forward button on the universal remote starts the robot moving forward and does not stop it until some other action occurs (e.g. the button is released, the stop button is pressed, the robot crashes). What I need to do is adapt my robot's one-step-at-a-time interface to the universal remote's continuous motion interface. That means the adapter pattern, yay!

The universal remote might expect a robot interface that looks something like:
library universal_remote;

import 'dart:async';

import 'robot.dart';

abstract class Ubot {
  Timer moveForward();
  Timer moveBackward();
  Timer moveLeft();
  Timer mvoeRight();
  void stop();
}
The Timer return type for those move actions could likely be adapted to something more domain specific, but they will suffice for now. For example, the moveForward() action continuously moves the robot forward until the returned timer is canceled. So timer objects will work in this first pass at the pattern.

I will move the robot adaptee forward once a second, so I grab a reference to a one second constant:
const oneSecond = const Duration(seconds: 1);
With that out of the way, I am ready to adapt my robot to the universal remote robot interface.

The adapter will need to accept an instance of the Robot being adapted:
class UbotRobot {
  Robot _robot;
  UbotRobot(this._robot);
}
A moveForward() can then make use of that private instance variable reference to the robot to move it forward (or "north" in Robot parlance) once every second:
class UbotRobot {
  Robot _robot;
  UbotRobot(this._robot);

  Timer moveForward() =>
    new Timer.periodic(oneSecond, (_){ _robot.move(Direction.NORTH); });
}
To implement the other directions, I would likely use a common private method along the lines of _move():
class UbotRobot {
  Robot _robot;
  UbotRobot(this._robot);

  Timer moveForward()  => _move(Direction.NORTH);
  Timer moveBackward() => _move(Direction.SOUTH);
  Timer moveLeft()     => _move(Direction.WEST);
  Timer mvoeRight()    => _move(Direction.EAST);
  Timer _move(dir) =>
    new Timer.periodic(oneSecond, (_){ _robot.move(dir); });
}
With that, I am ready to try things out in the client code. I grab an instance of the robot being adapted to supply it to the universal remote robot adapter:
  var robot = new Robot();
  var universalRobot = new UbotRobot(robot);
Next, I start moving forward, making sure to grab a reference to the control object (the returned Timer):
  print("Start moving the robot.");
  var btnCtrl = universalRobot.moveForward();
To simulate waiting 10 second before releasing the button, I create a 10 second timer. When the 10 second timer has elapsed, the callback cancels the timer and prints the robot's new location:
  // Simulate the button being release 10 seconds later...
  new Timer(
    new Duration(seconds: 10),
    (){
      btnCtrl.cancel();
      print("The robot is now at: ${robot.location}.");
    }
  );
And that does the trick, my procedural robot moves north once every second, winding up at 0, 10:
$ /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
  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, 10.
So in the end, a simple asynchronous adapter for procedural code was still pretty darn easy. Let's see what else I can throw at Dart to make this a little more difficult. Tomorrow.

Happy New Year everybody!

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


Day #50

No comments:

Post a Comment