Saturday, February 13, 2016

Wacky Successor Chain of Responsibility


Let's talk about the successor chain in the chain of responsibility pattern. In the purchasing power example that I have been using so far, the handler maintains responsibility for defining new links via a successor setter method:
abstract class PurchasePower {
  PurchasePower _successor;
  void set successor(PurchasePower successor) {
    _successor = successor;
  }
  // ...
}
This required client code to establish the links between instances:
  var manager = new ManagerPurchasePower();
  var director = new DirectorPurchasePower();
  var vp = new VicePresidentPurchasePower();
  var president = new PresidentPurchasePower();

  manager.successor = director;
  director.successor = vp;
  vp.successor = president;
I am OK with the approach here, but I am not a fan of the name successor. Really, in general, I am not a fan of using pattern names or participants in patterns—not even in examples. I much prefer domain specific names that are optionally annotated with pattern names.

In the case of organization purchase power, the successor would be better expressed as reports to / direct reports relationships:
abstract class PurchasePower {
  // Successor in the chain of responsibility
  PurchasePower _reportsTo;
  void set reportsTo(PurchasePower reportsTo) {
    _reportsTo = reportsTo;
  }
  // ...
}
Now when I look at the chain of responsibility being established, there is less mental translation between design pattern naming and the current domain:
  var manager = new ManagerPurchasePower();
  var director = new DirectorPurchasePower();
  var vp = new VicePresidentPurchasePower();
  var president = new PresidentPurchasePower();

  manager.reportsTo = director;
  director.reportsTo = vp;
  vp.reportsTo = president;
Everything about this feels more comfortable. Maybe there are people out there that look at a problem, tap their chin while considering things until finally shouting, "a-ha! the chain of responsibility will work here!" For me, the relationship comes first, after which I might come up with an approach to decide which member in the relationship needs to handle things. Then, if I am really paying attention, I might even think "ooh, that's the chain of responsibility!"

Hopefully if I keep plugging away at these patterns, I will get to the point at which they are more recognizable in situations like this. But I seriously doubt I will ever get to the chin-tapping state. And I am unsure that I would ever want to. I think the code is more maintainable with the domain names instead of the pattern names (and really, no one ever liked classes named abstract factory factory).

But I digress.

Another possible approach to the chain of responsibility is to exploit either the data structure in use or the domain. There is not really a data structure in play with this example, but the domain certainly implies a hierarchy—a manager will always report to a director, who will always report to a VP, etc. I can exploit that to eliminate the explicit assignment of reportsTo properties.

I start with a static mapping of runtime types pointing to the last used instance of that type:
abstract class PurchasePower {
  static Map<Type, PurchasePower> _instances = {};
  PurchasePower() {
    _instances[this.runtimeType] = this;
  }
  // ...
}
Any subclass of the PurchasePower handler will invoke the constructor here, caching the last instance of the give type. That is, when a ManagerPurchasePower instance is created, this superclass constructor will be called storing the instance index by the type.

The DirectorPurchasePower constructor can then make use of this, finding the most recent ManagerPurchasePower and assigning this instance as the reportsTo:
class DirectorPurchasePower extends PurchasePower {
  // ...
  DirectorPurchasePower() : super() {
    _directReport(ManagerPurchasePower);
  }
  // ...
}
After setting the default successor chain like this in the VP and President purchase power classes, I no longer have to explicitly specify the chain of responsibility. I simply create the various instances, start with the lowest and see which claims responsibility along the way:
  var manager   = new ManagerPurchasePower();
  var director  = new DirectorPurchasePower();
  var vp        = new VicePresidentPurchasePower();
  var president = new PresidentPurchasePower();

  var req = new PurchaseRequest(25*1000, "General Purpose Usage");
  manager.processRequest(req);
This particular recent instance storage and lookup implementation feels a little forced. Explicitly setting the chain was not too horrible and this approach is non-trivial. Still, it was good to explore the successor chain in a little more depth.

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


Day #94

No comments:

Post a Comment