Thursday, February 11, 2016

The Chain of Responsibility Pattern in Dart


How can anyone not love a pattern named chain of responsibility? I mean c'mon, the name invokes all sorts of linked interaction between objects that somehow manage to cooperate on ownership for an action of some sort. It's the kind of thing that makes bureaucracy so awful, but there's a pattern in programming that makes it elegant. So what's not to love?

I start my investigation of the pattern as I so often do, by copying the Java example on the Wikipedia page into lovely Dart code. The prime mover in the pattern—the interface used by the outside world—is the handler. It gets its name, as one might suspect, from its job to… handle a request (and then optionally forward the request on). The handler in the Wikipedia example describes purchasing power.

In Dart, the purchasing power interface looks like:
abstract class PurchasePower {
  PurchasePower _successor;
  void set successor(PurchasePower successor) {
    _successor = successor;
  }

  void processRequest(PurchaseRequest request);
}
The "chain" portion of this pattern is handled by the _successor private variable. If this PurchasePower class does not want to handle the request, the _successor is specified as the next in the chain. The processRequest() method holds the responsibility for handling the request. It receives a purchase request and must decide to handle it or forward the request on to the _successor.

The lowest level of purchasing power in the example is that of a lowly manager:
class ManagerPurchasePower extends PurchasePower {
  final double _allowable = 10 * 1000;

  void processRequest(PurchaseRequest request) {
    if (request.amount < _allowable) {
      print("Manager will approve $request.");
    }
    else if (_successor != null) {
      _successor.processRequest(request);
    }
  }
}

The upper limit that a manager can approve of is $10,000. If the request is for less than that, then the chain ends with the approval by the manager. If it is more than that, then the _successor is then asked to process the same request.

The object being processed by the pattern's handler is a purchase request:

class PurchaseRequest {
  double amount;
  String purpose;
  PurchaseRequest(this.amount, this.purpose);
  String toString() =>
    "\$${amount.toStringAsFixed(2)} for $purpose";
}

It requires a dollar amount and a description of the reason for the purchase. I provide a toString() method so that the entire object can be interpolated in a string (as it is when the manager approves the request).

That is the whole of the pattern. For it to be a true chain, I declare director, VP and presidential purchasing power. Anything that the president, who will be last in succession, cannot purchase needs board approval and not another successor:

class PresidentPurchasePower extends PurchasePower {
  final double _allowable = 60 * 1000;

  void processRequest(PurchaseRequest request) {
    if (request.amount < _allowable) {
      print("President will approve $request");
    }
    else {
      print("Your request for $request needs a board meeting!");
    }
  }
}
In client code, I can create each of these purchasing power objects:
  var manager = new ManagerPurchasePower();
  var director = new DirectorPurchasePower();
  var vp = new VicePresidentPurchasePower();
  var president = new PresidentPurchasePower();
Then I can specify the chain:
  manager.successor = director;
  director.successor = vp;
  vp.successor = president;
Lastly, I can parse the dollar amount being requested and start the chain on its merry path:
  var amount = (args[0] == null) ?
    1000 : double.parse(args[0]);

  var req = new PurchaseRequest(amount, "General Purpose Usage");
  manager.processRequest(req);
With that, I can quickly determine who to bug for the new company automobiles (including my Tesla):
$ ./bin/purchase.dart 5000 
Manager will approve $5000.00 for General Purpose Usage.
$ ./bin/purchase.dart 10000
Director will approve $10000.00 for General Purpose Usage
$ ./bin/purchase.dart 20000
Vice President will approve $20000.00 for General Purpose Usage
$ ./bin/purchase.dart 50000
President will approve $50000.00 for General Purpose Usage
$ ./bin/purchase.dart 75000
Your request for $75000.00 for General Purpose Usage needs a board meeting!

That was a nice start to exploration of the chain of responsibility pattern. I am already looking forward to kicking the tires on this pattern a little more.

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

Day #92

1 comment: