Thursday, February 4, 2016

Factory Method Pattern (Take 2)


Crazy what happens when you start writing a book, take a break for a year and a half, then come back. I had completely forgotten that I have already researched the factory method pattern for Design Patterns in Dart. But research it I did.

Crazy too is that the approach I was taking to design patterns back then is very different to the one that I have been doing on this recent chain of posts. My focus recently has been to understand the consequences of patterns and different approaches to the pattern. Back then I spent the entire time benchmarking. I think it is especially funny that I included benchmarks for a mirror based solution—some things don't change.

To gain a better understanding of the pattern, I start anew. For my initial example, I adapt the Java example maze from the Wikipedia page which itself was adapted from the C++ example in the Gang of Four book.

The prime mover in this pattern is the "creator" class. In the maze example, the MazeGame is the creator:
abstract class MazeGame {
  List<Room> rooms = [];

  MazeGame() {
    Room room1 = _makeRoom();
    Room room2 = _makeRoom();
    room1.connect(room2);
    addRoom(room1);
    addRoom(room2);
  }

  Room _makeRoom();

  void addRoom(Room r) { rooms.add(r); }
}
The constructor for this class creates 2 rooms, connects them, and adds the 2 rooms to the game. The _makeRoom() method is left abstract by virtue of having no method body. In other words, it is up to concrete implementations of this class to define how to make a room. This is the crux of the pattern. The MazeGame does not know the exact type of rooms with which it will be working, so it delegates that responsibility to subclasses.

The first concrete creator might create nothing but magic rooms in a magic maze:
class MagicMazeGame extends MazeGame {
  @override
  Room _makeRoom() => new MagicRoom();
}
A second concrete creator might create nothing but ordinary rooms for an early level in the game:
class OrdinaryMazeGame extends MazeGame {
  @override
  Room _makeRoom() => new OrdinaryRoom();
}
More interestingly, there is nothing stopping me from generating any kind of room in a subclass:
class CrazyMazeGame extends MazeGame {
  @override
  Room _makeRoom() =>
    new Random().nextBool() ? new OrdinaryRoom() : new MagicRoom();
}
That is the pattern in a nutshell. The things being created are "products" in this pattern. Products in this pattern have no requirements other than there must be multiple types—otherwise there is no reason for a subclass to choose. The Room product that will work with that above code might look like:
// Product
class Room {
  List connectedRooms = [];

  void connect(Room other) {
    connectedRooms.add(other);
  }
}

// Concrete Product #1
class MagicRoom extends Room {}

// Concrete Product #1
class OrdinaryRoom extends Room {}
With that, client code can make use of the creator class:
  MazeGame crazyGame = new CrazyMazeGame();
  print(crazyGame);
Which results in something like:
$ ./bin/maze.dart
CrazyMazeGame has 2 rooms: Instance of 'MagicRoom', Instance of 'OrdinaryRoom'
That will do for an introduction to the pattern. There are still implications to explore. I would also like to come up with a "modern web" example of the pattern. Way back when, I used this pattern in a Backbone.js knockoff, so I might do that again. It would be better, however, to come up with something a little more accessible to most programmers. Grist for forthcoming days.

Play with the code so far on DartPad: https://dartpad.dartlang.org/561d021f19910a79c194.


Day #85

No comments:

Post a Comment