Sunday, January 17, 2016

Proxies Don't Always Need to Know Subject Type


Up today, I explore types when implementing the proxy pattern in Dart. The Gang of Four book states that proxies do not necessarily need to know the type of the real subject. This seems reasonable to me, but I prefer to at least run the theory through somewhat practical application to see if I am overlooking something.

For the proxy class to not know the type of its real subject, the real subject must be created outside of the proxy and then supplied to the constructor. For the car example that I had been using previously, the client code for this might look something like:
  Automobile realCar =  new Car();
  Automobile proxy = new ProxyAutomobile(realCar);
  proxy.drive();
The proxy class does not need to know the specific class, but it needs to have an interface that is being implemented—the proxy class needs to know that the interface supports a common set of actions.

The subject in this pattern remains the Automobile interface, which declares that all implementations must support the drive() method:
// Subject
abstract class Automobile {
  void drive();
}
Next, I declare three different real subjects that will be used by the proxy class:
// Real Subjects
class Car implements Automobile {
  void drive() {
    print("Car has been driven!");
  }
}

class Truck implements Automobile {
  void drive() {
    print("Truck has been driven!");
  }
}

class Motorcycle implements Automobile {
  void drive() {
    print("Motorcycle has been driven!");
  }
}
The proxy class is still a protection proxy from the other night, so it requires both a type of Automobile and a Driver when constructed:
// Proxy Subject
class ProxyAutomobile implements Automobile {
  Driver _driver;
  Automobile _auto;

  ProxyAutomobile(this._auto, this._driver);
  // ...
}
As for the drive() method itself, it needs to first verify that the driver is legal, then will invoke the drive() method on the real subject:
class ProxyAutomobile implements Automobile {
  // ...
  void drive() {
    if (_driver.age <= 16)
      throw new IllegalDriverException(_driver, "too young");

    _auto.drive();
  }
}
And that works exactly as expected. A driver that is of age can drive any of these automobiles through the proxy class:
  // Proxy will allow access to real subject
  driver = new Driver(25);
  print("== $driver here:");
  new ProxyAutomobile(new Car(),        driver)..drive();
  new ProxyAutomobile(new Truck(),      driver)..drive();
  new ProxyAutomobile(new Motorcycle(), driver)..drive();
The output of that code confirms that the 25 year-old driver can driver each of these:
$ ./bin/drive.dart
== 25 year-old driver here:
Car has been driven!
Truck has been driven!
Motorcycle has been driven!
And a 16 year-old driver results in an illegal driver exception:
  driver = new Driver(16);
  print("== $driver here:");
  new ProxyAutomobile(new Car(), new Driver(16))..drive();
  // => == 16 year-old driver here:
  //    Unhandled exception:
  //    IllegalDriverException: 16 year-old driver is too young!
I mentioned earlier that when the proxy does not know the real subject's type ahead of time, then client code needs to supply the real subject. That is not strictly necessary because... mirrors! Instead of supplying the real subject, the client code can supply the type, or even a symbol representation of the type:
  new ProxyAutomobile(#Car,        driver)..drive();
  new ProxyAutomobile(#Truck,      driver)..drive();
  new ProxyAutomobile(#Motorcycle, driver)..drive();
The proxy class can then use this to create a real subject:
class ProxyAutomobile implements Automobile {
  Driver _driver;
  Symbol _autoType;

  ProxyAutomobile(this._autoType, this._driver);

  Automobile get auto => _autoMirror.reflectee;

  InstanceMirror get _autoMirror =>
    _classMirror.newInstance(new Symbol(''), []);

  ClassMirror get _classMirror =>
    currentMirrorSystem().
      findLibrary(#car).
      declarations[_autoType];

  void drive() {
    if (_driver.age <= 16)
      throw new IllegalDriverException(_driver, "too young");

    auto.drive();
  }
}
Sure that's some gnarly ClassMirrorInstanceMirrrorreflectee code, but it is possible. And some of us really enjoy mirrors for some reason.

I am more or less done with the proxy pattern in Dart now. I may have another go at remote proxies because I was never quite satisfied with my previous efforts. The Gang of Four book also mentions smart references as a possible application of the pattern. I am hard pressed to come up with an illustrative example in a garbage collected language. Regardless, the proxy pattern with unknown types works well.

Play with the (pre-mirror) code on DartPad: https://dartpad.dartlang.org/6a163b3c6708564bb13b.

Day #67

No comments:

Post a Comment