Friday, November 13, 2015

Factory Class vs. Factory Constructor in Flyweight


Unneeded patterns excite me at least as much as the tried-and-true ones. As I mentioned in the kickoff post for this latest chain, I am eager to explore a modern take on Design Patterns through the lovely lens of Dart.

I was reasonably happy with last night's implementation of the Flyweight pattern. As pointed out to me by the esteemed Matthew Butler, I might be missing some Dart goodness that could clean up my code. Don Olmstead also reported “quite a few style nits looking through your code,” but I'm pretty sure that guy's just crazy (I kid! I'm using dartfmt from now on).

At any rate, Matthew's most interesting suggestion was to convert last night's LampFactory class into a factory constructor. I think he has a valid point. The factory class was responsible for caching the flyweight:
// dartfmt'd -- happy Don? :P
class LampFactory {
  var cache = {};
  Lamp findLamp(color) {
    return cache.putIfAbsent(color, () => new Lamp(color));
  }

  int get totalCount => cache.length;
}
The flyweight class in this simple example described a lamp of varying color:
class Lamp {
  String color;
  Lamp(this.color);
}
Per Matthew's suggestion, I can eliminate the factory class in favor of the Dart factory constructor:
class Lamp {
  String color;
  static Map _cache = {};

  factory Lamp(color) =>
    _cache.putIfAbsent(color, () => new Lamp._(color));

  Lamp._(this.color);

  static int get totalCount => _cache.length;
}
That saves a few lines of code (mostly the class declaration for the factory), but I dunno. The Lamp class seems muddled now that it does two, distinct things. Maybe it is better with the caching code and the “intrinsic lampness” of the flyweight separated:
class Lamp {
  static Map _cache = {};
  static int get totalCount => _cache.length;

  factory Lamp(color) =>
    _cache.putIfAbsent(color, () => new Lamp._(color));

  String color;
  Lamp._(this.color);
}
Were I writing this in real life, I would likely leave it as is. And it does retain the spirit of the flyweight pattern—reducing the number of objects with the same intrinsic properties.. In the spirit of "patterns" however, I remain unsure.

What I do know is that this approach significantly improves the Tree context class. Instead of a bunch of references to factory-this, factory-that:
class Tree {
  int count = 0;
  var _lampFactory;

  Tree() {
    _lampFactory = new LampFactory();
  }

  void hangLamp(color, branchNumber) {
    new TreeBranch(branchNumber)
      ..hang(_lampFactory.findLamp(color));

    count++;
  }

  String get report => "Added ${count} lamps.\n"
      "Used ${_lampFactory.totalCount} kinds of lamps.";
}
The Tree now looks exactly like it would if the flyweight pattern were not in play:
class Tree {
  int count = 0;

  void hangLamp(color, branchNumber) {
    new TreeBranch(branchNumber)
      ..hang(new Lamp(color));

    count++;
  }

  String get report => "Added ${count} lamps.\n"
      "Used ${Lamp.totalCount} kinds of lamps.";
}
That cleanliness seems very much worth a little muddiness in the flyweight class.

In fact, I can use the same approach to convert the TreeBranch class into a flyweight as well—without making a change to the Tree context class:
class TreeBranch {
  static Map _cache = {};
  static int get totalCount => _cache.length;

  factory TreeBranch(number) =>
    _cache.putIfAbsent(number, () => new TreeBranch._(number));

  int number;
  TreeBranch._(this.number);
  // ...
}
Were I to stick with the factory class approach, I would have to introduce more factory objects into the calling context that already seemed lousy with them.

So my preliminary conclusion is that yes, factory constructors do seem of value with the Flyweight pattern in Dart. The ability to implement in-place without changing the context is nice, but I most appreciate the cleanliness of the resulting context code. I have the benefits of the flyweight pattern without the ugliness of factory instance variables polluting things.


Day #2

No comments:

Post a Comment