Friday, February 5, 2016

Factory Method without Factory Subclasses


I am already bored by the factory method pattern. Time for Dart mirrors! I kid of course. About the bored, not the mirrors. I goes without saying that I'm going to add mirrors.

The Gang of Four book mentions two major varieties of the pattern: one that uses an abstract class, forcing subclasses to define the factory implementations, and the other in which the creator class provides a default factory implementation. I tried the the former last night, so tonight I try with a default implementation.

For this example, I adopt the Delphi example on the Wikipedia page, which describes a game factory. For this example, two players want to play a series of games against each other. The creator for that might look like:
// Creator
class GameFactory {
  String playerOne, playerTwo;
  GameFactory(this.playerOne, this.playerTwo);
  String toString() => "*** $playerOne vs. $playerTwo ***";

  // The factory method
  BoardGame createBoardGame([String game]) {
    if (game == 'Checkers') return new CheckersGame();
    if (game == 'Thermo Nuclear War') return new ThermoNuclearWar();
    return new ChessGame();
  }
}
The constructor requires the names of both players. Once they are ready to play a game, they might choose a game from a text based listing of games, passing the name to createBoardGame(). In this example, there are no subclasses for the creator—the GameFactory knows how to create all products.

The "products" in this version of the pattern are different board games that can be played in the system. Each will need a list of pieces that can be played:
// Product
class BoardGame {
  List playerOnePieces = [];
  List playerTwoPieces = [];

  String get winner => new Random().nextBool() ? 
    "Player One" : "Player Two";
  String toString() =>
    "  ${this.runtimeType}\n"
    "    Player One has: ${playerOnePieces.join(', ')}\n"
    "    Player Two has: ${playerTwoPieces.join(', ')}\n"
    "    Winner: $winner\n";
}
So a chess game would look like:
class ChessGame extends BoardGame {
  List playerOnePieces = [
    '1 king', '1 queen', '2 rooks', '2 bishops', '2 knights', '8 pawns'
  ];
  List playerTwoPieces = [
    '1 king', '1 queen', '2 rooks', '2 bishops', '2 knights', '8 pawns'
  ];
}
There is some redundancy there, but I will worry about that another time. For now, I have my single creator class and various product classes. Which means that I am ready for some client code.

I start with the creator, which starts the game series between two players:
  var series = new GameFactory('Professor Falken', 'Joshua');
  print(series);
Then they can play various games, choosing each from the text listing on the main menu:
  game = series.createBoardGame('Checkers');
  print(game);

  game = series.createBoardGame('Thermo Nuclear War');
  print(game);

  game = series.createBoardGame();
  print(game);
Running this would result in something like the following output:
$ ./bin/board_game.dart
*** Professor Falken vs. Joshua ***
  CheckersGame
    Player One has: 12 pieces
    Player Two has: 12 pieces
    Winner: Player Two

  ThermoNuclearWar
    Player One has: 1,000 warheads
    Player Two has: 1,000 warheads
    Winner: None

  ChessGame
    Player One has: 1 king, 1 queen, 2 rooks, 2 bishops, 2 knights, 8 pawns
    Player Two has: 1 king, 1 queen, 2 rooks, 2 bishops, 2 knights, 8 pawns
    Winner: Player One
So there you go: a factory method pattern example in which the creator is a concrete class, provided a default product, and can supply all products. It does not feel terribly different from last night's example. In fact, it feels almost too simple to warrant the creator class. Except for the need to remember the two players' names, this could almost be done in client code.

I am unsure if this is the example that I would like to use in Design Patterns in Dart as it is a tad simplistic. Then again, there are some opportunities for improving the code that may make it more illustrative. That's something for tomorrow. Plus mirrors!

Play with the code on DartPad: https://dartpad.dartlang.org/0018524156ade602d043.


Day #86

No comments:

Post a Comment