I've constructed a bridge in a client. I've constructed a bridge in a constructor. So tonight, I construct a bridge in a factory.
I must investigate the factory and abstract factory in Dart. Mostly because I am unsure how much value the standard structure of the pattern is needed in a language with factory constructors baked right in. Since they are baked in, that's what I opt for tonight.
The abstraction in my bridge pattern example continues to be a
Messenger
:abstract class Messenger { Communication comm; Messenger(this.comm); void updateStatus(); }The purpose of classes implementing this interface is to simply facilitate status updates. There are a number of communication methods bridged from this abstraction: HTTP, websockets, etc. In last night's pass at the bridge pattern, the specific implementation was chosen in the abstraction's constructor (i.e.
Messenger
) and subsequently changed when certain thresholds were reached. Tonight, I move the responsibility of choosing the implementation out of the abstraction and into a factory constructor. Because it make sense and is so darn easy, I declare a factory constructor in the
Communication
implementation:import 'dart:math' show Random; // Implementor abstract class Communication { factory Communication() => new Random().nextBool() ? new WebSocketCommunication() : new HttpCommunication(); void send(String message); }I continue to find it strange that an abstract class can declare a constructor—even if it is a factory constructor. Each time, I must remind myself that it works for just this kind of case: choosing a subclass implementation. In this case, I randomly choose between the
WebSocketCommunication
and HttpCommunication
concrete implementations.No changes are required to either of the concrete implementations. They both continue to establish their connections to the server and define the appropriate code to send messages. The
Messenger
abstraction and the subclass refined for use in web clients, WebMessenger
do need to change slightly. The subclass no longer needs to concern itself with the
Communication
implementation. The Messenger
can do that. Instead, the subclass can worry only about web page related things, like the text input element from which it will obtain messages to send back to the server:class WebMessenger extends Messenger { InputElement _messageElement; WebMessenger(this._messageElement); // ... }The
Messenger
abstraction needs to do a little more work now, but not much. In addition to declaring the Communication
instance variable, it now assigns it in the constructor:abstract class Messenger { Communication comm; Messenger() : comm = new Communication(); void updateStatus(); }On a sidenote, I remain skeptical of initializer lists in constructors like that. I am trying to use them to see if they grow on me, but I think it just makes things noisy (especially if a constructor body is added to the mix).
That pretty much does it. Through several page reloads, I find that the client does indeed randomly switch between implementations:
$ ./bin/server.dart [WebSocket] message=asdf [WebSocket] message=asdf [WebSocket] message=asdf [HTTP] message=asdf [WebSocket] message=asdf [HTTP] message=asdfThat was fairly straight-forward and low on the surprise scale. I note before concluding that none of this precludes me from setting the
Communciation
implementation in the client code. For example:main() { var message = new WebMessenger(query('#message')) ..comm = new HttpCommunication(); // ... }I also note that making more purposeful choices than the current random implementation selection is straight-forward. I can simply choose based on VM info, browser info, or any other bit of information on which one might opt between HTTP and websocket communication. Any or all of those choices would be right at home in the factory constructor. Or perhaps someday in an abstract factory constructor.
Code for the bridge client and the backend server are on the Design Patterns in Dart public repository.
Day #82
No comments:
Post a Comment