Friday, June 17, 2016

Using the factory pattern in a Dart abstract class

/*
 * This example demonstrates the use of interrelated parallel inheritance
 * hierarchies: a subclass in one hierarchy (A) contains a reference to
 * the base class of another hierarchy (B). When (A) subclass is
 * instantiated its reference to (B) is assigned an object of the correct
 * subclass of (B).
 *
 * In this case, when a CatStudio is instantiated, it knows to populate
 * its reference to Animal with a Cat.
 *
 * While it's possible to simply specify the Cat class in CatStudio, this
 * would force the AnimalStudio class to know about Cat.
 */

library app.models;

import "dart:mirrors";

class CatStudio extends AnimalStudio {
  final animalType = "Cat";
  CatStudio() : super();
}

class Cat extends AnimalBase {
  Cat() : super("Meow");
}

/* When instantiating a new AnimalStudio subclass in client code, it is
 * only necessary to specify animalType as a final String in order to
 * get an instance of the desired Animal subclass assigned to the
 * `animal` attribute. In turn, AnimalBase is not required to know
 * anything about Animal subclasses defined in the client code.
 */
abstract class AnimalStudio {
  Animal animal;
  final String animalType;

  String record() {
    print("$animalType: ${animal.sing()}");
  }

  AnimalStudio() {
    animal = new Animal(animalType);
  }
}

/* A factory is required here as the calling code (AnimalStudio) knows
 * only the name of the Animal subclass to instantiate. Dart's reflection
 * system is queried to retrieve, and instantiate, the Animal class
 * defined in the client code. The file with client code must contain
 * the "library app.models" statement.
 */
abstract class Animal {
  final String sound;
  String sing() => sound;

  factory Animal(String type) {
    LibraryMirror lib = currentMirrorSystem().findLibrary(
        new Symbol('app.models'));
    Map<Symbol, Mirror> classes = lib.declarations;
    ClassMirror cls = classes[new Symbol(type)];
    InstanceMirror inst = cls.newInstance(new Symbol(''), []);
    return inst.reflectee;
  }
}

/* This is the class that must be extended in client code when defining
 * Animal subclasses. When the `Animal` factory is called with the
 * `animalType` parameter, the constructor of the desired subclass is
 * called. This will result in the *generative* constructor of the
 * superclass being called. This would be `Animal()`, which doesn't
 * exist. A workaround for this is presented in various places which
 * calls for creating a generative constructor with a mangled name,
 * such as `_Animal()`. This is abstruse, however, and the presentation
 * herein is considered idiomatic Dart as seen in the standard libraries,
 * etc (reference needed).
 */
class AnimalBase implements Animal {
  final String sound;
  String sing() => sound;
  AnimalBase(this.sound);
}

void main() {
  new CatStudio().record();
}

No comments:

Post a Comment