Sunday, January 24, 2016

Is It a Real Proxy Pattern Without Types?


It turns out that I may be too rigid at times with my types in Dart. That's just insane. Me. An old Perler, an ancient Rubyist, and old-timey JavaScripter, someone who fled Java because of the type craziness — using types too darn often. What a world.

When I first explored protection proxy patterns I almost headed down the path of generic proxies, but decided against it because... types. The particular example that I was using to explore protection proxies probably influence me to a fair extent. I was using a Driver to determine if a particular driver instance was old enough to legally start a Car. I opted against a generic proxy class (probably rightly) since a proxy class protecting against drivers was certain to be an automobile of some kind. Following from there, if the proxy class always worked with automobiles, it might as well implement the Automobile interface.

That suited me just fine because it kept me on the happy Gang of Four path. All of my proxy pattern explorations followed the same patterns as in the original book: a subject (the interface), a real subject (e.g. a Car) and a proxy (e.g. RemoteCar). But in Gilad Bracha's The Dart Programming Language, it struck me reading that, not only was it OK to use a generic proxy, but that "being able to define transparent proxies for any sort of object is an important property."

So, I go back to my protection proxy example to see how it will work with a proxy that will work with any kind of object. In addition the object serving as the real subject of my protection proxy, I also need a driver instance through which access will be allowed or denied:
class ProxyProtect {
  final Driver _driver;
  final _realSubject;

  ProxyProtect(this._driver, this._realSubject);
  // ...
}
As a quick aside, I must point out that I really enjoy Gilad's book. It is wonderful getting insights into the language from one of the primary designers. The little notes about things like variables almost almost always being used in a final way are wonderful. I will make a concerted effort to use final in most of my real code. I likely won't use it teaching for the same reason that it is not the default in the language—it is not expected by most developers. Anyhow...

I have my ProxyProtext constructor, now I need the calls to the real subject. I continue to use noSuchMethod() for this:
import 'dart:mirrors' show reflect;

@proxy class ProxyProtect {
  // ...
  dynamic noSuchMethod(i) {
    if (_driver.age <= 16)
      throw new IllegalDriverException(_driver, "too young");

    return reflect(_realSubject).delegate(i);
  }
}
If no other methods are defined, then Dart will invoke noSuchMethod() with information about the method being invoked. With that, no matter what method is invoked, I first check the driver. If the driver is too young, an exception is thrown and nothing else occurs. The real subject is protected against illegal actions. If the driver is of age, then it is time for mirrors—the kind that allow dynamic calls and inspection. In this case, I get a mirror of the real subject with reflect(), then delegate whatever was invoked to the real subject with delegate().

Easy peasy. Now I have a ProxyProtect for any object that might have a driver: a car, an R/C toy, a train, as spaceship, etc. If I create a car and an of-age driver in client code, I can drive the car:

  var _car = new Car(),
      _driver = new Driver(25);
  print("* $_driver here:");

  var car = new ProxyProtect(_driver, _car);
  car.drive();
When run, this results in:
$ ./bin/drive.dart
* 25 year-old driver here:
Car has been driven!
If an underage drive attempts to pilot the vehicle:
  var _car = new Car(),
      _driver = new Driver(16);
  print("* $_driver here:");

  var car = new ProxyProtect(_driver, _car);
  car.drive();
Then this results in:
$ ./bin/drive.dart
* 16 year-old driver here:
Unhandled exception:
IllegalDriverException: 16 year-old driver is too young!
As I found out last night, I am not quite done here. When I run the code through the Dart static type analyzer, I find that my protection proxy does not seem to fit the correct types. Specifically, a drive() method is being invoked when one is not declared and the class does not explicitly specify an interface that it is implementing:
[hint] The method 'drive' is not defined for the class 'ProxyProtect' (/home/chris/repos/design-patterns-in-dart/proxy/bin/drive.dart, line 19, col 7)
To address this, I use the built-in @proxy annotation, which tells the analyzer to give my proxy class a pass:
@proxy class ProxyProtect {
  // ...
}
With that, I have a working protection proxy for any sort of object... and it passes static type analysis. Nothing too surprising here, though I do have figure out how and if to work this into the discussion of the pattern in Design Patterns in Dart. For now, I think I may be done with my exploration of the proxy pattern—it was a fun one! Play with the code on DartPad: https://dartpad.dartlang.org/6d8f76cab3f9d1bb97ff. Day #74

No comments:

Post a Comment