Friday, February 12, 2016

Chain of Responsibility, but with noSuchMethod


Most of the fun of the chain of responsibility pattern is building the chain. One of the implementations suggested in the Gang of Four book involves Smalltalk's doesNotUnderstand. In Dart, that means it is time for some noSuchMethod() fun.

In the purchasing power example that I adapted from Wikipedia, the handler currently supports the chain with:
abstract class PurchasePower {
  PurchasePower _successor;
  void set successor(PurchasePower successor) {
    _successor = successor;
  }

  void processRequest(PurchaseRequest request) {
    if (_successor != null) {
      _successor.processRequest(request);
    }
  }
}
If a concrete handler sets the successor, then the processRequest() method in this handler base class will forward the request on to the next handler in the chain.

For example, the manager-level purchasing power handler will handle requests for less than $10k:
class ManagerPurchasePower extends PurchasePower {
  final double _allowable = 10 * 1000.0;

  void processRequest(PurchaseRequest request) {
    if (request.amount < _allowable) {
      print("Manager will approve $request.");
      return;
    }
    super.processRequest(request);
  }
}

If the request is for more, then the superclass implementation of processRequest() is invoked, passing the request along to the next object in the chain. The nice thing about this approach is that a subclass that does not want to handle a request can simply omit a declaration of processRequest(), allowing the superclass to forward the request on to the chain successor.

So what does noSuchMethod() buy me in this scenario? Not a ton, but it does yield a more generalized approach to handling requests. In the baseclass, I replace the previous processRequest() with:

import 'dart:mirrors' show reflect;
abstract class PurchasePower {
  // ...
  noSuchMethod(args) {
    if (_successor == null) return;
    reflect(_successor).delegate(args);
  }
}

If a concrete class does not define a method like processRequest() (or invokes it directly in the superclass), then the invocation winds up in noSuchMethod(). If there is no successor in the chain, then nothing is returned, terminating the chain. Otherwise, thanks to the magic of delegate(), the successor is reflected so that the method being invoked can be sent to the successor along with any arguments.

With that, when processRequest() in concrete handlers invokes the method of the same name in the superclass, noSuchMethod() kicks in, forwarding the request along to the successor. The behavior is exactly the same as with an explicit processRequest() in the superclass, only noSuchMethod() will work with any method, instead of a single method.

So, in the end, I don't know that this buys me much, except when multiple handlers are involved. Or when one simply wants to have fun with mirrors.

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

Day #93

1 comment: