Tuesday, December 29, 2015

Class Adapters in Dart: A Vague Attempt


Time to pick another pattern to explore in Dart. Up today, the adapter pattern, a nice structural pattern as a change of pace from the behavioral ones that I have been exploring.

This is unique among the pattern in the Gang of Four book in that it has both class and object implementations described in the book. I should start with the object implementation because I think I understand how to implement it already. But where's the fun in that?

I am going to stick with abstract class names to get started. The Adaptee is the class that needs to be adapted to support an alternate interface. It would typically exist in a separate library since I could change it if it existed in a library that I could alter. Let's start with:
library adaptee;

class Adaptee {
  void specificRequest() {
    print("[Adaptee] A specific request.");
  }
}
Next, I have the interface that I need to support in the form of a Target class:
library adapter;

import 'adaptee.dart';

class Target {
  void request() {
    print("[Target] A request.");
  }
}
Presumably, I have a bunch of classes in this library that implement this interface. Now I need an adapter class that implements this interface, but still allows access to the methods in Adaptee. The class adapter approach used in the Gang of Four book uses multiple inheritance: public for the target interface and private for the adaptee interface. Dart does not support multiple inheritance (public or private). It supports multiple interface implementations, but that does not give me access to methods in both the target and adaptee.

I could try mixins—extending (subclassing) the target and mixing in the adaptee:
class Adapter extends Target with Adaptee {
  void request() {
    print("[Adapter] doing stuff..");
    specificRequest();
  }
}
That works... to a point. If I create an instance of the adapter in client code then access the request() method from the target:
main() {
  var adapter = new Adapter();
  adapter.request();
}
Then I get my desired output—the adapter request() method executes and then invokes specificRequest() from the adaptee:
$ ./bin/adapt.dart
[Adapter] doing stuff..
[Adaptee] A specific request.
There are two problems with this approach. First the adaptee's methods are now available publicly on the adapter:
main() {
  var adapter = new Adapter();
  adapter.request();
  // Can access adaptee methods from client code:
  adapter.specificRequest();
}
Which results in:
$ ./bin/adapt.dart
[Adapter] doing stuff..
[Adaptee] A specific request.
[Adaptee] A specific request.
The second problem is more serious. This will only work if the adaptee does not declare a constructor. If it does:
class Adaptee {
  Adaptee() { print("Adaptee!"); }
  void specificRequest() {
    print("[Adaptee] A specific request.");
  }
}
Then I get a warning:
[error] The class 'Adaptee' cannot be used as a mixin because it declares a constructor
Worse, I get runtime error:
$ ./bin/adapt.dart                                  
'package:adapter_code/adapter.dart': error: line 9 pos 35: mixin class 'Adaptee' must not have constructors

class Adapter extends Target with Adaptee {
                                  ^
In other words, this will not fly in all but extremely limited circumstances.

In the end, the best approach that I can think of is to implement the target interface and subclass the adaptee:
class Adapter extends Adaptee implements Target {
  void request() {
    print("[Adapter] doing stuff..");
    specificRequest();
  }
}
This is not ideal since the adaptee's specificRequest() is still public on the adapter class. If I really, really needed a class adapter, this might serve in a pinch.

That said, it seems like an object adapter is the only real option in Dart, so I will get to that tomorrow.

Play with the code on DartPad: https://dartpad.dartlang.org/ee43829831bffb807350.


Day #48

No comments:

Post a Comment