Sunday, January 10, 2016

Dart doesNotUnderstand Proxies


Patterns can be kinda dull. Consequences and implementations are pretty damn fun.

I got the proxy pattern working in Dart last night without much trouble. Let's see if I can cause trouble with the second implementation (a.k.a. doesNotUnderstand) from the Gang of Four book chapter on the pattern. The doesNotUnderstand method is a Smalltalk construct, but I think Dart's noSuchMethod ought to serve a similar purpose.

The Gang of Four's doesNotUnderstand implementation was framed as a generic solution. I am going to keep mine somewhat generic. I continue to use last night's protection proxy example for driving automobiles. The protection came in the form of preventing underage drivers from getting behind the wheel:
// Proxy Subject
class ProxyCar implements Automobile {
  // ...
  void drive() {
    if (_driver.age <= 16) {
      print("Sorry, the driver is too young to drive.");
      return;
    }

    _car.drive();
  }
}
Instead of protection solely in the drive() method, I am going to switch to a noSuchMethod() implementation. This protection proxy follows standard proxy practices in that it does not create an instance of the real subject (the car) until it is needed. The car getter method returns the private _car instance if it has been defined, otherwise it creates and assigns it:
class ProxyCar implements Automobile {
  Driver driver;
  Car _car;

  ProxyCar(this.driver);

  Car get car => _car ??= new Car();
  // noSuchMethod will go here...
}
To make the noSuchMethod() approach work, I am going to need to invoke arbitrary methods on the real subject. That means that I need to import dart:mirrors:
import 'dart:mirrors';
I next need to delete the existing drive() method. Once gone, invoking the drive() method on ProxyCar will send the call to noSuchMethod(). If the noSuchMethod() method is not defined in ProxyCar, then the call gets sent to the noSuchMethod() in ProxyCar's superclass, Object. Object's noSuchMethod() throws a NoSuchMethodError, which I do not want, so I declare noSuchMethod() in ProxyCar. With dart:mirrors, I can reflect on the car, then send the message and arguments that reached noSuchMethod() to the car instance:
class ProxyCar implements Automobile {
  // ...
  dynamic noSuchMethod(i) {
    if (driver.age <= 16)
      throw new IllegalDriverException(driver, "too young");

    return reflect(car).invoke(i.memberName, i.positionalArguments);
  }
}
I have switched to an exception here to really ensure that this registers as something wrong. With that, my client code is once again working. I create a 25 year-old driver, then send the drive() message to the proxy car:
  // Proxy will allow access to real subject
  print("* 25 year-old driver here:");
  car = new ProxyCar(new Driver(25));
  car.drive();
Since drive() is not defined on ProxyCar, it gets sent to noSuchMethod(), which checks that the driver's age is greater than 16, then tells the car to drive:
$ ./bin/drive.dart
* 25 year-old driver here:
Car has been driven!
If a 16 year-old tries to get behind the wheel, the noSuchMethod() guard clause kicks in, giving me the appropriate exception:
$ ./bin/drive.dart
* 16 year-old driver here:
Unhandled exception:
IllegalDriverException: 16 year old driver is too young!
I can add other gaurd clauses to noSuchMethod() as well. For example, I can guard against the same conditions as in the Gang of Four example—illegal messages:
class ProxyCar implements Automobile {
  // ...
  dynamic noSuchMethod(i) {
    if (i.memberName != #drive)
      throw new IllegalAutomobileActionException(i.memberName);
    if (driver.age <= 16)
      throw new IllegalDriverException(driver, "too young");

    return reflect(car).invoke(i.memberName, i.positionalArguments);
  }
}
Now if the driver tries to do something wrong with the car, an exception will arise regardless of age:
  print("* 25 year-old driver here:");
  car = new ProxyCar(new Driver(25));
  car.fly();
  // ==> IllegalAutomobileActionException: Symbol("fly")
The Gang of Four makes this completely generic, but I am hard pressed to think of a situation in which I could define a list of valid methods that would not apply to a single interface (like Automobile in this case). Regardless, this noSuchMethod() proxy approach works quite nicely—I think I will be using this in the future.

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

Day #60

No comments:

Post a Comment