Wednesday, December 30, 2015

An Object Adapter (Pattern) in Dart


It would have been fun to come up with a class adapter pattern in Dart. Sadly, the best that I could come up with is of very limited use. Even though I usually get in trouble when I say this, I suspect that the object adapter will be easy to implement. Let's see how much trouble that causes.

I am still working with generic implementation names for my classes. Target describes the simple interface that I am trying to support:
class Target {
  void request() {
    print("[Target] A request.");
  }
}
So all my adaptee needs to support is the request() method. Currently, however, it resides in a separate library over which I nominally have no control:
library adaptee;

class Adaptee {
  void specificRequest() {
    print("[Adaptee] A specific request.");
  }
}
In the sample code in the Gang of Four book, client code creates an instance of the adaptee to supply to the adapter. To follow along with that example, I need something along the lines of:
import 'package:adapter_code/adaptee.dart';
import 'package:adapter_code/adapter.dart';

main() {
  var adaptee = new Adaptee();
  var adapter = new Adapter(adaptee);

  // Invoke a target method, not available on the adaptee:
  adapter.request();
}
Then, to implement the adapter class, I need to accept an Adaptee argument in the constructor and define a request() method that, among other things, invokes a specific method on that Adaptee:
class Adapter extends Target {
  Adaptee _adaptee;

  Adapter(this._adaptee);

  void request() {
    print("[Adapter] doing stuff..");
    _adaptee.specificRequest();
  }
}
Of note, I extend Target rather than implement its interface. The end result is the same in this case since I redefine request. Still, this might come in handy should I need access to another helper method from Target.

Whoops! That's some premature generalization if ever there was some. I note that I could do that if needed, but, for now, I only implement the interface:
class Adapter implements Target {
  Adaptee _adaptee;

  Adapter(this._adaptee);

  void request() {
    print("[Adapter] doing stuff..");
    _adaptee.specificRequest();
  }
}
With that, I have my object adapter. Running the client code results in some work in the adapter and some in the adaptee:
$ ./bin/adapt.dart                                             
[Adapter] doing stuff..
[Adaptee] A specific request.
One of last night's laments—that the class adapter exposed the adaptee's methods publicly—is no longer applicable here. The only publicly supported method is request() from the target interface. Of course, the client code still has access to the adaptee's specificRequest() method via adaptee. The point is to maintain proper encapsulation, not to prevent access, so this is acceptable.

So for once I did not jinx myself. The adapter pattern in object form really is pretty darn easy. There are a couple of not-too-surprising implications (implement the target until extending is required, the adaptee is available in the client context, encapsulation is resolved), but this really went pretty much as expected. Darn it.

Maybe a specific case tomorrow will provide a little more excitement!

Play with the boring code yourself on DartPad: https://dartpad.dartlang.org/0cbca96caaa3ac9f95da.



Day #49

No comments:

Post a Comment