Saturday, November 14, 2015

Flyweight Coffee Shop in Dart


The current Flyweight pattern example with which I have been playing is vaguely unsatisfying to me. I co-opted a tree decoration example from the Wikipedia article and it seemed a nice start. I know it is just an example, but it does not feel like something from the real-world. Plus it really seemed like it had two flyweights in there, confusing things.

Eventually, I'd like to pull find some real-world examples (anyone know where flyweight was used in live Dart code?), but for now, I am going to play with the Scala example from the same Wikipedia article. It is a coffee-ordering example so that ought to feel more real.

Borrowing from the Scala coffee shop example, I would to be able to deliver a bunch of orders to different people. At the end of the day, I would like to be able to tally up some totals. The main thread of the application should look something like:

main() {
  var shop = new CoffeeShop()
    ..order('Cappuccino', 'large',  who: 'Fred', fooFoo: 'Extra Shot')
    ..order('Espresso',   'small',  who: 'Bob')
    ..order('Frappe',     'large',  who: 'Alice')
    ..order('Frappe',     'large',  who: 'Elsa', fooFoo: '2 Percent Foam')
    ..order('Coffee',     'small')
    ..order('Coffee',     'medium', who: 'Chris');

  shop.serve();
  print('-----');
  print(shop.report);
}
The flyweight in this example will be the coffee "flavor" (Cappuccino, Espresso, Coffee, etc.). Intrinsic to the coffee flavor is the name and maybe some nutritional information or cost data. The rest of the information is specific to the order: who placed it, the size, and any silly instructions.

The Order class would be something simple along the lines of:
class Order {
  CoffeeFlavor flavor;
  int size;
  String instructions;
  String who;

  Order(this.flavor, this.size, this.who, this.instructions);

  String get service => "${flavor.name} to ${who}";
}
I am going to stick with last night's factory constructor (instead of the previous night's factory class). So the flyweight + factory is:
class CoffeeFlavor {
  static Map _cache = {};
  static int get totalCount => _cache.length;

  factory CoffeeFlavor(name) =>
      _cache.putIfAbsent(name, () => new CoffeeFlavor._(name));

  String name;
  CoffeeFlavor._(this.name);
}
What's left is the CoffeeShop class which is responsible for keeping a list of orders for serving and for end-of-day reporting. To handle orders as above, the order() method should look something like:
  void order(name, sizeName, {who, fooFoo}) {
    var flavor = new CoffeeFlavor(name);
    int size = sizes[sizeName];
    _orders.add(new Order(flavor, size, who, fooFoo));
  }
The rest of the class declares the private list of _orders and a mapping of size names to fluid ounces:
class CoffeeShop {
  static Map sizes = {
    'small': 8,
    'medium': 12,
    'large': 20
  };

  List _orders = [];

  void order(name, sizeName, {who, fooFoo}) {
    var flavor = new CoffeeFlavor(name);
    int size = sizes[sizeName];
    _orders.add(new Order(flavor, size, who, fooFoo));
  }
}
Next, I need a method to serve drinks:
  void serve() {
    _orders.forEach((o) { print("Served ${o.service}.");});
  }
Finally, I can define a simple report getter to produce some daily stats:
  String get report => "Served ${_orders.length} coffee drinks.\n"
      "Served ${CoffeeFlavor.totalCount} kinds of coffee drinks.\n";
With that, I can run a coffee shop:
Served Cappuccino to Fred.
Served Espresso to Bob.
Served Frappe to Alice.
Served Frappe to Elsa.
Served Coffee to null.
Served Coffee to Chris.
-----
Served 6 coffee drinks.
Served 4 kinds of coffee drinks.
That is very similar to what I had the previous night with the tree example, but it does have a more real world feel to it. At least to this coffee drinker.

What I am keen to explore next is building with additional properties that are intrinsic to the CoffeeFlavor flyweight object. For instance, what if each flavor had a profit of $USD0.10 per fluid ounce except for "Frappe" drinks, which are $USD0.50? That could be represented in the flyweight as a profitPerFluidOunce getter method:
class CoffeeFlavor {
  //...
  float get profitPerOunce => name == 'Frappe' ? .5 : 0.1;
}
The Order class can then determine profit for each order instance:
class Order {
  //...
  float get profit => size * flavor.profitPerOunce;
}
Now the coffee shop report can quickly include the day's take:
class CoffeeShop {
  // ...
  String get report => "Served ${_orders.length} coffee drinks.\n"
      "Served ${CoffeeFlavor.totalCount} kinds of coffee drinks.\n"
      "For a profit of: \$${profit}";

  float get profit {
    var sum = 0;
    _orders.forEach((o){ sum += o.profit; });
    return sum;
  }
}
Which results in a profit of a little over $USD24 for a handful of drinks:
Served 6 coffee drinks.
Served 4 kinds of coffee drinks.
For a profit of: $24.8
Nice!

I appreciate this example's "real world" feel. What I most appreciate about it is the obvious ability to add intrinsic properties to the flyweight—and a quick way to use them.

(Try it on DartPad: https://dartpad.dartlang.org/c5d130cf24ddbbab6a11)

Day #3


No comments:

Post a Comment