Tuesday, June 24, 2014

Default Implementation in Dart Factory Method


Up tonight, I continue my exploration of the Factory Method pattern in Dart. Tonight, I explore a variation in which a default implementation is provided in the base creator class.

Before starting, I note that my current framework sample code may be insufficient for adequately describing the various use-cases of Factory Method. The framework Factory Method comes from the Hipster MVC framework (well, toy framework largely written in support of Dart for Hipsters). The abstract creator in the framework is the collection part of the MVC. It is aptly named HipsterCollection:
abstract class HipsterCollection extends IterableBase {
  // ...
  HipsterModel modelMaker(attrs);
  // ...
}
This abstract creator expects concrete creators to define modelMaker() with appropriate concrete products—in this case the product being a type of HipsterModel, the model in the MVC framework. The sample code that I have been using is a collection of comics books. In this case, ComicBook serves as the concrete product class, extending HipterModel. The concrete creator class, ComicCollection, then subclasses HipsterCollection, defining the modelMaker() factory method as:
class Comics extends HipsterCollection {
  modelMaker(attrs) => new ComicBook(attrs);
  // ...
}
That implementation of the Factory Method pattern works just fine for my framework. The Hispter MVC framework and the application that contains the code defining the abstract portions of the framework are completely separate. One can be a publicly hosted package and the other an application in a private repository. The framework delegates the knowledge of the product class to that private application which is ideal since the publicly available framework should not have any knowledge of application code.

Although this works fine, it does present a slight problem. Programmers using my framework will often wind up with a number of very small creator classes like Comics:
class Comics extends HipsterCollection {
  modelMaker(attrs) => new ComicBook(attrs);
  get url => '/comics';
}
As the Gang of Four book puts it, “Subclassing is fine when the client has to subclass the creator class anyway, but otherwise the client now must deal with another point of evolution.” In my current Comics, I don't really need to subclass HipsterCollection. To be sure, HipsterCollection should expect to be subclassed so that custom events can be set, but in this particular example, I could get away with what the Gang of Four describe as a default implementation.

Unfortunately, here I hit the limitation of my example code. It makes no sense for my public framework to be aware of how to create ComicBook instances. I should explore some of the parameterized examples from the Gang of Four, but I will leave that for another day and another code source. For now, I use a variation in which the client code stores the product class in a static variable, providing a useful default implementation.

In this case, the client code no longer creates an instance of ComicsCollection:
var my_comics_collection = new Collections.Comics();
Instead, it first defines the class to be used when creating models and then creates an instance of HipsterCollection:
HipsterCollection.modelClass = Models.ComicBook;
var my_comics_collection = new HipsterCollection()..url = '/comics';
To support that, HispterCollection can no longer be abstract. Instead it needs to support a reasonable default—using the modelClass static variable:
class HipsterCollection extends IterableBase {
  // ...
  static Type modelClass = HipsterModel;

  HipsterModel modelMaker(attrs) =>
    reflectClass(modelClass).
      newInstance(const Symbol(''), [attrs]).
      reflectee;

  String url;
  // ...
}
Unfortunately, this means the return of Dart mirrors. That might not be a horrible solution in some cases, but I ought to be able to avoid it under most circumstances. This current implementation precludes multiple collection types since HipsterCollection.modelClass can only have one value at a time (I could cache the class in an instance variable, but this already feels awkward).

I think tomorrow it is time to explore the global Map of factory closures proposed in comments from previous nights' posts. Then I need to work up an illustrative example of Factory Method in a single codebase so that some of these other approaches might work—without resorting to mirrors.



Day #102

No comments:

Post a Comment