Monday, February 15, 2016

How Much Mixin for the Chain of Responsibility?


After last night, I am again a fan of mixins in Dart. Mixins will not solve all problems, but they seem to solve several cases of chain of responsibility pattern nicely. I still have some follow-up questions, though. Up tonight how much of the chain can I get into the mixin class (and how much should I get in there)?

The mixin in my current example adds purchasing power traits to an Employee class. Give that Employee supports a reportsTo property, the _PurchasingPower mixin delegates the chain of reponsibility's successor to this property:
abstract class _PurchasePower {
  get successor => reportsTo;
  // ...
}
If the handler method in the chain is unable to process the current request, it will forward it along to this property. For a manager, this property might point to a director. For a director, it might point to a VP.

As for handling the request, I currently do that via two methods:
abstract class _PurchasePower {
  // ...
  void processRequest(PurchaseRequest request) {
    if (_processRequest(request)) return;
    if (successor == null) return;

    successor.processRequest(request);
  }

  bool _processRequest(_) => false;
}
The processRequest() method is responsible for the successor chain while _processRequest() is responsible for attempting to handle the actual request. The default implementation in the mixin is for _processRequest() to not handle the request. In practice, concrete handler classes are expected to override this behavior.

I appreciate the separation of focus between the two methods. It is nice that subclasses only have to worry about overriding the processing of the request instead of handling the request and managing the successor chain. When _PurchasePower is mixed into the Manager class, for instance, it is completely unaware of the chain of responsibility:
class Manager extends Employee with _PurchasePower {
  final double _allowable = 10 * 1000.0;

  bool _processRequest(PurchaseRequest request) {
    if (request.amount > _allowable) return false;

    print("Manager will approve $request.");
    return true;
  }
}
It either handles the request or it does not. Simple.

And yet, I note that the manner in which it handles the request is very similar to the manner in which the Director class handles the same request:
class Director extends Employee with _PurchasePower {
  final double _allowable = 20 * 1000.0;

  bool _processRequest(PurchaseRequest request) {
    if (request.amount > _allowable) return false;

    print("Director will approve $request");
    return true;
  }
}
The only thing that varies in the implementation of _processRequest() is the name of the employee that potentially has the appropriate purchasing power. The Employee base class can figure this out from the runtime type of the object:
class Employee {
  // ...
  String get title => "${this.runtimeType}";
  String toString() => title;
}
By defining the toString() method, I tell Dart how to interpolate this object into any string. Making use of that in the _PurchasePower mixin, I can declare a common _processRequest() handler method as:
abstract class _PurchasePower {
  final double _allowable = 0.0;
  // ...
  bool _processRequest(PurchaseRequest request) {
    if (request.amount > _allowable) return false;

    print("$this will approve $request.");
    return true;
  }
}
My _PurchasePower chain of responsibility mixin is getting crowded now. That said, the payoff is pretty nice. The employee subclasses with purchasing power now only need to declare the maximum allowed amount that they can handle:
class Manager extends Employee with _PurchasePower {
  final double _allowable = 10 * 1000.0;
}

class Director extends Employee with _PurchasePower {
  final double _allowable = 20 * 1000.0;
}

class VicePresident extends Employee with _PurchasePower {
  final double _allowable = 40 * 1000.0;
}
The mixin takes care of both processing the request and handling the successor chain. Even with this wonderfully compact implementation, I still retain the ability to override the default behavior as when the President always halts the chain:
class President extends Employee with _PurchasePower {
  final double _allowable = 60 * 1000.0;

  void processRequest(PurchaseRequest request) {
    if (request.amount > _allowable) {
      print("Your request for $request needs a board meeting!");
    }
    else {
      print("President will approve $request");
    }
  }
}
Since there is no successor beyond President I override the processRequest() method, handling the request directly.

And yes, the chain of responsibility still works as desired:
$ ./bin/purchase.dart 2000 
Manager will approve $2000.00 for General Purpose Usage.
$ ./bin/purchase.dart 12000
Director will approve $12000.00 for General Purpose Usage.
$ ./bin/purchase.dart 22000
VicePresident will approve $22000.00 for General Purpose Usage.
$ ./bin/purchase.dart 52000
President will approve $52000.00 for General Purpose Usage
$ ./bin/purchase.dart 82000
Your request for $82000.00 for General Purpose Usage needs a board meeting!
So the answer to tonight's question would seem to be that, if the process handler is sufficiently generic, I can put everything into the chain mixin. The resulting concrete handler implementations are wonderfully succinct. There is barely any readability cost in the mixin class in this example thanks to Dart's toString() method. And I still retain the ability to override behavior in subclasses should that be required. Certainly this will not always be possible, but when there is sufficiently common process handling, this is a nice little code win.

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


Day #96

No comments:

Post a Comment