My many adventures with the proxy pattern in Dart continues. After day 1, I workable, if ugly code. After day 2, I have nicer, if not-quite-a-proxy-pattern code. So tonight I hope to actually implement a somewhat nice looking version of an actual remote proxy across Dart isolates.
Let's see how I can mess that up.
The setup between the main worker and the isolate worker is pretty typical isolate setup. The main worker holds a
ProxyCar
and will communicate to a real Car
in the isolate. It starts by creating a ReceivePort
on which it can listen for messages from the isolate, spawns the isolate, then waits for the first message back from that isolate which will be a SentPort
to send messages back to the isolate:main() async { var r = new ReceivePort(); var receiveStream = r.asBroadcastStream(); await Isolate.spawn(other, r.sendPort); SendPort s = await receiveStream.first; // Remote car stuff will follow... }Similarly, the
other()
isolate that holds a real Car
instance accepts the SendPort
from main()
and creates its own ReceivePort
to send to main()
:other(SendPort s) { var r = new ReceivePort(); var receiveStream = r.asBroadcastStream(); s.send(r.sendPort); new Car(receiveStream, s); }Thanks to the very nice async / await syntax in Dart, I was able to clean up a lot of initial async messiness. But now I realize that my remote proxy class does not match the real subject. The
ProxyCar
class returns Future
instances for car actions:class ProxyCar implements Automobile { // ... Future drive() => _send(#drive); Future stop() => _send(#stop); // ... }The real subject (and the interface that both classes implement) declares
start()
and stop()
as returning nothing:class Car implements Automobile { // ... void drive() { state = 'driving'; } void stop() { state = 'idle'; } }Interestingly (at least to me) is that the Dart type analyzer does not consider this a problem. I would still feel more comfortable if the remote proxy class and the real subject class both implemented the same interface.
There is nothing to be done about the
ProxyCar
interface. Communication across isolates is inherently asynchronous. So I will experiment with the adapter pattern, adapting a synchronous Car
class to an asynchronous version. This might not be the best idea, but...It does have the advantage of producing a nice, clear
Car
class:// Adaptee class Car implements Automobile { String state = 'idle'; void drive() { state = 'driving'; } void stop() { state = 'idle'; } }For the remote proxy, I define a new interface for both the
AsyncCar
and ProxyCar
classes to implement, this time with futury goodness:// Subject abstract class AsyncAuto implements Automobile { String get state; void drive(); void stop(); }The
AsyncCar
class then become both the adapter and real subject. The async messiness as well as the send and receive ports pile into it:// Real Subject & Adapter class AsyncCar implements AsyncAuto { SendPort _s; ReceivePort _r; Car _car; AsyncCar(this._r, this._s) { _car = new Car(); _r.listen((message) { print("[AsyncCar] $message"); if (message == #drive) _car.drive(); if (message == #stop) _car.stop(); _s.send(state); }); } String get state => _car.state; Future drive() => new Future((){ _car.drive(); }); Future stop() => new Future((){ _car.stop(); }); }Not much has changed in there from the previous two nights aside from
state
, drive()
, and stop()
methods which now forward requests to a concrete Car
instance. The drive()
and stop()
methods also return Future
instances for completeness.Nothing in the
ProxyCar
class needs to change other than the interface that it implements, which the new AsyncAuto
interface:// Proxy Subject class ProxyCar implements AsyncAuto { // ... }Last, I change the isolate to create an
AsyncCar
:other(SendPort s) { var r = new ReceivePort(); var receiveStream = r.asBroadcastStream(); s.send(r.sendPort); new AsyncCar(receiveStream, s); }With that, I have more traditional version of the remote proxy in which both the remote subject and real subject implement the same interface. I appreciate that the
Car
class is very clean now. Aside from that, I am unsure that I have bought myself any improvements in code readability or maintainability. So, for now, I would have to rate the benefit of this exercise inconclusive... but fun.Day #63
No comments:
Post a Comment