Sunday, January 3, 2016

I've been hard at work, or at least at work, since my last blog post hammering the Polymer and Dart Codelab project into shape to my liking. I set myself upon a quest to abstract out as much as possible from the example Admin Console interface so as to ease the development of new interfaces. Sure, I could have just resigned myself to copy and past the entire project for each new project, but that would involve a lot of text substitutions and room for errors. Besides, how much fun is that?

The problem

After factoring out much of the code from the Codelab* classes and <codelab-*> custom elements into their respective Item* and <item-*> subclasses and getting it mostly working, there was the nasty problem of handling the situation wherein a superclass element such as ItemElement needs to instatiate a new Item based on the its own type. For example, CodelabElement::resetForm() requires that a new Codelab be instantiated to populate the new form. Since we're moving as much code as possible into the base classes, I wanted to also have CodelabElement's superclass ItemElement create the new Codelab instance. But how to do this without ItemElement requiring knowledge about its subclasses? I needed a way to instantiate a new Codelab based only on a text string to the superclass Item constructor.

Factory and mirrors to the rescue

Dart actually has the factory pattern built into the language. To use it, a superclass method is declared using the 'factory' keyword:

import 'package:polymer/polymer.dart';
import 'dart:mirrors';


class Item extends Observable {
  String _title;
  String _description;

  static const MIN_TITLE_LENGTH = 10;
  static const MAX_TITLE_LENGTH = 30;
  static const MAX_DESCRIPTION_LENGTH = 140;

  String get title => _title;
  set title(String s) => _title = s;

  String get description => _description;
  set description(String s) => _description = s;

   /// Allow a subclass instance to be generated with
   /// an empty argument list
  Item.created(this._title, this._description);

  factory Item(String type, String title, String description) {
    if(type == null)
      return; // Todo: Determ how this is being called w/ no type
    MirrorSystem libs = currentMirrorSystem();
    LibraryMirror lib = libs.findLibrary(new Symbol('polymer_and_dart.web.models'));
    Map<Symbol, Mirror> classes = lib.declarations;
    ClassMirror cls = classes[new Symbol(type)];
    InstanceMirror inst = cls.newInstance(new Symbol(''), [title, description]);
    return inst.reflectee;
  }
}
The code is pretty self-explanatory - the mirror system finds a list of class declarations in the "polymer_and_dart.web.models" namespace, and a ClassMirror instance is created with the one we want. This mirror is then used to invoke newInstance(), which returns a mirror on the new Codelab instance, contained in its "reflectee" attribute.

Some minor gotchas appeared when working out how to actually implement this in an inheritance hierarchy. For one, the concept of mixins and mixin applications needs to be clear in the mind of the developer. There's a decent discussion of how this works out in practical terms in the SO discussion at putting a factory method in an abstract class.

I need to think about this some more. There's the interesting aspect of this regarding making superclass attributes final...

No comments:

Post a Comment