Tuesday, January 5, 2016

Extending... er, Implementing… er, Replacing Timer


The timer in my Dart implementation of the adapter pattern bugs me. More to the point, its very existence annoys me.

The asynchronous interface of the robot currently returns Timer instances for the various movement actions:
class UniversalRemoteRobot {
  // ...
  Timer moveForward()  => _executeControlledCommand(_ubot.moveForward);
  Timer moveBackward() => _executeControlledCommand(_ubot.moveBackward);
  Timer moveLeft()     => _executeControlledCommand(_ubot.moveLeft);
  Timer moveRight()    => _executeControlledCommand(_ubot.moveRight);
  // ...
}
This exposes the underlying implementation detail of the movement commands. I am moving procedural robots once per second with a periodic Timer. The outside world only need a command runner object to be used when canceling movement. The Timer class includes a cancel() method, so this works, but exposes too much information.

I have tried replacing the Timer with a quick subclass, but that has proven unsuccessful. Tonight, I take a closer look. This ought be easy, after all.

I start by defining a simple Runner class that subclasses Timer:
class Runner extends Timer {}
Even with that simple declaration, I get a bunch of warnings from the type analyzer:
[error] The generative constructor 'Timer(Duration duration, () → void callback) → Timer' expected, but factory found
[warning] Missing concrete implementation of 'Timer.cancel' and getter 'Timer.isActive'
[warning] The class 'Runner' does not have a constructor 'periodic'
All right, I understand that I need the periodic() named constructor, but what gives with the rest of that? Why do I need to implement cancel() again—can't the superclass' implementation handle that? And what's with that factory constructor warning...?

Oh, dear. I see what the problem is. All of the constructors in Timer are factory constructors. That means my subclass needs to define its own factory or generative constructors to implement Timer. Bother.

Well, not that much of a bother, I suppose, but I would have preferred a simpler extends than this, if only to keep any complexity out of the supporting code. In live code, minor complexity is no big deal. In illustrative code, it can be a discussion killer.

I may stick with vanilla Timer in the end, but let's see what it looks like. The lack of public constructors likely means that I can implement a Runner independent of Timer, but I will start by at least implementing the interface (the isActive and cancel() action are both useful for running commands):
class Runner implements Timer {
  Timer _t;
  Runner.periodic(Duration d, Function cb) {
    _t = new Timer.periodic(d, cb);
  }
  bool get isActive => _t.isActive;
  void cancel() { _t.cancel(); }
}
I still need a Timer instance to move my robots once a second. If Timer won't give it to me, I create my own instance variable. The two Timer methods can then delegate to this instance. I still need a regular, unnamed constructor. I can actually use a redirecting constructor here... and get a little fancy:
const oneSecond = const Duration(seconds: 1);

class Runner implements Timer {
  Timer _t;
  Runner(Function cb): this.periodic(oneSecond, (_){cb();});
  Runner.periodic(Duration d, Function cb) {
    _t = new Timer.periodic(d, cb);
  }
  // ...
}
My movement commands all take zero arguments while a periodic timer callback requires one argument (the timer, so it can be canceled). While redirecting the main constructor, I convert the supplied callback from a zero argument callback to a zero argument callback that is wrapped by a one argument function. This means I can create new command runners as: new Runner(command)).

That's nice. It is fun to play with redirecting constructors, but it is completely unnecessary in this case. I can remove the extends Timer constraint and make things much clearer:
const oneSecond = const Duration(seconds: 1);

class Runner {
  Timer _t;
  Runner(Function cb) {
    _t = new Timer.periodic(oneSecond, (_){ cb(); });
  }
  bool get isActive => _t.isActive;
  void cancel() { _t.cancel(); }
}
So, in the end, it was not a big deal replacing the Timer class with something similar, but a little closer to the domain. I do wonder why Timer only provides factory constructors. I would hazard a guess that this has to do with compiling to JavaScript and the underlying setTimeout() implementation. It may not be a big deal, but it is not something that just works™ like so many things in Dart. Thankfully, it only requires a little noodling through.


Day #55


No comments:

Post a Comment