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