Monday, February 8, 2016

Silly Factories in the Factory Method Pattern


Tonight, I look into using Dart factory constructors in the factory method pattern. Since they both have the word "factory" in them they can probably be used together, right? Well, probably not, but I can no longer resist pairing the two.

Normal generative constructors in Dart behave like constructors in just about any object-oriented language. They create a new instance of the current class, optionally setting some instance variables. Factory constructors, on the other hand, are responsible for building and initializing the object being created. This means that factory constructors can return anything—not limited to the current class.

Take, for instance, a Person class:
class Person {
  int age;
  Person(this.age);
}
When a new Person is instantiated with new Person(21), the generative constructor creates an instance of Person and assigns the age instance variable to 21. Easy peasy.

Now I introduce two subclasses (both still using plain-old generative constructors):
class Child extends Person {
  Child([age=12]) : super(age);
}

class Adult extends Person {
  Adult([age=18]) : super(age);
}
There is nothing special here. Both classes default to an age appropriate for the type of person being created. What these allow me to do is declare a (named) factory constructor back in Person:
class Person {
  factory Person.random() => 
    new Random().nextBool() ? new Child() : new Adult();
}
This Person.random() constructor creates and returns full-blown object. It cannot rely on the language to generate the object, instead it has to do all of the work itself. With that, I can randomly generate children and adults from the Person superclass:
  new Person.random();
  // => Instance of 'Adult'
  new Person.random();
  // => Instance of 'Adult'
  new Person.random();
  // => Instance of 'Child'
What is really weird about these factory constructors is that they do not have to return an instance of the current class or a subclass. Instead, I can declare a Person constructor that returns a string:
class Person {
  // ...
  factory Person.string() { return "A person"; }
}
And that works perfectly fine:
  new Person.random();
  // => Instance of 'Adult'
  new Person.random();
  // => Instance of 'Adult'
  new Person.random();
  // => Instance of 'Child'
  new Person.string();
  // => 'A person'
To be completely honest, that is not "perfectly fine." The Dart type analyzer does complain:
[warning] The return type 'String' is not a 'Person', as defined by the method 'string'
But despite the warning, the code compiles and runs (see this DartPad).

Anyhow, back to the task at hand. How can I use these factory constructors in the factory method pattern? Well, I probably cannot since one is a constructor and the other a method, but let's do it anyway. I currently have a GameFactory class that creates a bunch of different board game products:
class GameFactory {
  // ...
  // The factory method
  createBoardGame([String game]) {
    if (game == 'Checkers') return new CheckersGame();
    if (game == 'Thermo Nuclear War') return new ThermoNuclearWar();
    return new ChessGame();
  }
}
Instead of a factory method, I can use a factory constructor:
class GameFactory {
  // ...
  factory GameFactory.createBoardGame([String game]) {
    if (game == 'Checkers') return new CheckersGame();
    if (game == 'Thermo Nuclear War') return new ThermoNuclearWar();
    return new ChessGame();
  }
}
Since BoardGame is not a subclass of GameFactory, the Dart type analyzer complains something fierce about this. But it compiles and runs anyway:
  new GameFactory.createBoardGame('Checkers').play();
  new GameFactory.createBoardGame('Thermo Nuclear War').play();
  new GameFactory.createBoardGame().play();
So what does this buy me? Like war, absolutely nothing.

In fact, I lose the ability to associate the factory constructor board games with the rest of the series. Each of those three board games are completely independent of the game factory series, which can no longer keep a reference to each for a final score:
  var series = new GameFactory('Professor Falken', 'Joshua');
  series.start();

  new GameFactory.createBoardGame('Checkers').play();
  new GameFactory.createBoardGame('Thermo Nuclear War').play();
  new GameFactory.createBoardGame().play();
So in the end, factory constructors and the factory method pattern are amusing together, but I can see no practical benefit. It was still totally worth messing around with them though!

Play with the amusing code on DartPad: https://dartpad.dartlang.org/4a59d56472d11cde9354.


Day #89

3 comments:

  1. Hi, apologies, not at all relevant here, but don't know how to contact you. Tried to buy your Patterns in Polymer book with PayPal, but it throws me out with a 404, after entering PayPal password. Cheers, Steve

    ReplyDelete
    Replies
    1. Ugh. That's a problem on PayPal's side which is mostly beyond my control. If it's still doing it now, shoot me an email at chris@patternsinpolymer.com and we'll see if we can come up with a workaround. Apologies for the hassle.

      -Chris

      Delete