Design patterns - Factory

From Juneday education
Jump to: navigation, search

Description

In this chapter, we'll discuss various versions of the so called factory pattern. We focus on three variations, which popularly go under the names

  • Simple Factory
  • Factory Method
  • Abstract Factory

These creational patterns exist to isolate and protect client code from the details on how objects are created. We want to encapsulate the way objects are created to a so called factory object.

When client code needs an object, it uses a factory object rather than creating the object of desire itself. The factory object knows how to create instances of concrete classes implementing some interface (or inheriting some abstract super class).

The goals of the versions of this pattern is to insulate client code from changes. This is accomplished using various techniques, which we'll walk you through briefly here and in the video lectures.

Simple Factory

This pattern uses an interface for a factory and a concrete class implementing the factory. The purpose of the factory is to make a choice of what class to use when client code requests an instance of some abstract class or interface.

In the example, we are looking back at our adventure game and how we could create a Character for the game. Character is an abstract base class. The client code should not be concerned with how to choose between the concrete classes for concrete types of Characters. This is accomplished using an interface for the CharacterFactory and one concrete factory class SimpleCharacterFactory. There are only two methods in the CharacterFactory interface:

public interface CharacterFactory {
  public Character createCharacter(CharacterType character,
                                   WeaponType    weapon,
                                   String        name);

  public Character createUnarmedCharacter(CharacterType character,
                                          String        name);
}

The concrete factory, SimpleCharacterFactory accomplishes the implementation using a switch-case on the argument character of type CharacterType (which is an enum). The enum is simple:

public enum CharacterType {
  KNIGHT,
  TROLL,
  ORCH
}

When the client code wants a Character instantiated, they only have to provide the enum type. This insulates the client code from knowing too much about the classes and how they are instantiated. It is of course much easier to look up the enum CharacterType. The WeaponType is a similar enum:

public enum WeaponType {
  CLUB,
  SWORD,
  SHOTGUN,
  UNARMED
}

The factory now only has to make a switch-case statement around the enum for CharacterType and return a new concrete character, e.g. a Troll or a Knight.

Here's the implementation:

public class SimpleCharacterFactory implements CharacterFactory {

  private static WeaponFactory weaponFactory 
    = new SimpleWeaponFactory();

  public Character createCharacter(CharacterType character,
                                   WeaponType weapon,
                                   String name) {
    switch(character) {
    case TROLL:
      return new Troll(name, weaponFactory.createWeapon(weapon));
    case KNIGHT:
      return new Knight(name, weaponFactory.createWeapon(weapon));
    case ORCH:
      return new Orch(name, weaponFactory.createWeapon(weapon));
    default:
      return null; // can't happen
    }
  }

  public Character createUnarmedCharacter(CharacterType character,
                                          String name) {
    return createCharacter(character, WeaponType.UNARMED, name);
  }
}

We round off this example in the lecture by discovering that it isn't a perfect pattern. Whenever we want to add a new character class, we've got to change and recompile both the enum CharacterType and the switch-case loop in the factory. The client code, however, is fairly immune to these changes.

Factory Method

This is one step in the right direction when improving the simple factory pattern. In this version of the factory pattern, the idea is to simplify the factory interface and remove the swith-case (or if-else if you used that instead) from the concrete factory.

This is accomplished through creating several concrete factories, one for each concrete class of what should be produced. So the CharacterFactory interface now becomes:

public interface CharacterFactory {

  public Character createCharacter(WeaponType    weapon,
                                   String        name);
  public Character createUnarmedCharacter(String name);

}

Note that the CharacterType parameters are removed. Instead of a centralized factory responsible for choosing concrete implementation from a parameter, we will now have one concrete factory per concrete character class. This is for instance what the TrollFactory looks like:

public class TrollFactory implements CharacterFactory {

  private static WeaponFactory weaponFactory = new SimpleWeaponFactory();

  public Character createCharacter(WeaponType weapon,
                                   String name) {
    return new Troll(name, weaponFactory.createWeapon(weapon));
  }

  public Character createUnarmedCharacter(String name) {
    return createCharacter(WeaponType.UNARMED, name);
  }
}

Internally, the TrollFactory uses a simple factory for the weapon, but that is an implementation detail. We could use the same pattern also for weapons. The client code now becomes simpler too, thanks to the removal of the CharacterType variable. The client instead chooses to use an appropriate factory for the job:

public class Game {

  public static void main(String[] args) {
    CharacterFactory cFactory = new TrollFactory();
    Character troll = 
      cFactory.createCharacter(WeaponType.CLUB,
                               "Lillfjant");
    cFactory = new KnightFactory();
    Character sirJames =
      cFactory.createCharacter(WeaponType.SWORD,
                               "Sir James");
    Character blackKnight =
      cFactory.createCharacter(WeaponType.UNARMED,
                               "Fistfighting Black Knight");
    cFactory = new OrchFactory();
    Character evilOrch =
      cFactory.createCharacter(WeaponType.SHOTGUN,
                               "Ugly scary Orch");
    Character unarmedOrch =
      cFactory.createUnarmedCharacter("Ugly scary unarmed Orch");
    System.out.println(troll);
    System.out.println(sirJames);
    System.out.println(blackKnight);
    System.out.println(evilOrch);
    System.out.println(unarmedOrch);
  }
}

As you see, the factories can be reused to create as many characters (of the same concrete type) as we want. When new character types are added, we just create new factories. This does not violate the open-closed principle. The CharacterFactory interface is closed for modification (we can't change it without breaking client code and factories) but open for extension. Adding a new character type requires a new factory but doesn't mean that we have to change any existing code.

Abstract factory

Abstract factory is explained differently in different sources. We've chosen a version which we believe is the most typical one. The thing we want to accomplish here is a factory which produces more than one type of abstract products. For the adventure game, we wrote as an example a factory which can produce both weapons and characters. They are different objects but still related - they occur in the game.

Again, we start with an interface for the factory. In our example, the factory creates a list of Characters and a list of Weapons. The lists only hold references of the abstract super types Character and WeaponBehavior respectively. Now, the concrete factories can produce different actual concrete characters and weapons, referred to by the lists' elements.

This allows us to create three different configurations of the game. One configuration for a game with difficulty "Easy", one for "Hard" and one for "Nightmare". The list of characters will be the opponents (bad guys) which the player will encounter during the game. Of course, there are more and more dangerous characters when playing in "Nightmare" than when playing in "Easy" or "Hard".

This is what the abstract factory interface looks like in this example:

import java.util.List;
public interface GameEntitiesFactory {

  public List<Character>createBadGuys();
  public List<WeaponBehavior>createWeapons();

}

As you see, there are only two methods. One for creating the list of opponents and one for creating the list of weapons which the player might find in the game (so the player can upgrade weapon if needed).

Then we have three concrete factories implementing the GameEntitiesFactory interface, EasyEntitiesFactory, HardEntitiesFactory and NightmareEntitiesFactory. This is what the EasyEntitiesFactory looks like:

import java.util.List;
import java.util.ArrayList;

public class EasyEntitiesFactory implements GameEntitiesFactory {

  // Only knights and a few trolls in easy level
  private static CharacterFactory knightFac = new KnightFactory();
  private static CharacterFactory trollFac  = new TrollFactory();
  // The weapons for the easy level
  private static WeaponFactory weaponFac     = new SimpleWeaponFactory();

  // Three knights and one troll
  public List<Character>createBadGuys() {
    List<Character> badGuys = new ArrayList<>();
    badGuys.add(knightFac
                .createCharacter(WeaponType.SWORD, "Sir Humpty"));
    badGuys.add(knightFac
                .createCharacter(WeaponType.SWORD, "Sir Dumpty"));
    badGuys.add(knightFac
                .createCharacter(WeaponType.SWORD, "Sir James"));
    badGuys.add(trollFac
                .createCharacter(WeaponType.CLUB, "Trolly"));
    return badGuys;
  }

  // A club, a sword and a shotgun
  public List<WeaponBehavior>createWeapons() {
    List<WeaponBehavior>weapons = new ArrayList<>();
    weapons.add(weaponFac.createWeapon(WeaponType.CLUB));
    weapons.add(weaponFac.createWeapon(WeaponType.SWORD));
    weapons.add(weaponFac.createWeapon(WeaponType.SHOTGUN));
    return weapons;
  }
}

As you see, the factory internally uses factory method to create the actual characters for the list. And for the weapons list, it uses a simple factory for the creation of weapons. It is not uncommon that factories uses a mix of factory styles like this, but it is not necessary.

As for the client code, we wrote a stupid test class which loops through the opponents in some level (like Easy), and where a player fights the various opponents. As the simulation goes on, we also let the player find weapons and upgrade its weapon (using the changeWeapon() method). Here's the test class with the simulation:

import java.util.List;

public class Game {

  public static void main(String[] args) {

    CharacterFactory cFactory = new KnightFactory();
    Character player =
      cFactory.createUnarmedCharacter("Sir Playsalot");
    GameEntitiesFactory gef = new EasyEntitiesFactory();
    List<Character>    opponents = gef.createBadGuys();
    List<WeaponBehavior> weapons = gef.createWeapons();
    int weaponIndex=0;

    System.out.print(player);
    System.out.println(" enters the woods");
    for(Character c : opponents) {
      System.out.println("<<<Oh no, not another one!");
      System.out.println(player + " meets " + c + " who attacks!");     
      while(c.health() > 0) {
        c.fight(player);
        sleep(200);
        player.fight(c);
        sleep(200);
        player.fight(c);
        sleep(200);
        player.fight(c);
        sleep(500);
      }
      System.out.println(c + " is now dead!!!!!!!!");
      sleep(3000);
      System.out.println("==============================");
      System.out.println(player + " finds a magic potion and drinks it");
      player.takeDamage(-100);
      if(weaponIndex < weapons.size()) {
        System.out.println(player + " finds " +
                           weapons.get(weaponIndex)
                           + " and upgrades his weapon!");
        player.changeWeapon(weapons.get(weaponIndex));
        weaponIndex++;
      }
    }
    System.out.println("===========GAME OVER=============");
    System.out.println("Final score: " + player);
  }

  static void sleep(int millis) {
    try {
      Thread.currentThread().sleep(millis);
    } catch (InterruptedException e) {}
  }
}

The part of the client code above which is relevant to the abstract factory pattern is of course the creation of the lists of characters and weapons. The only line which needs to change, in order to get lists from a different difficulty level (like nightmare) is this line:

GameEntitiesFactory gef = new NightmareEntitiesFactory();
// could be:              new EasyEntitiesFactory();
// or:                    new HardEntitiesFactory();

The point we hope to communicate is that the abstract factory can be used to allow for several different factories representing different configurations. Of course, we could make a factory for producing also the EntitiesFactories. With a simple factory, the code would become:

GameEntitiesFactory gef = Levels.getEntetiesFactory(Levels.EASY);

That would completely decouple the client code from the creation of different configurations of characters and weapons. This style, with a factory factory (so to speak) is sometimes presented as an essential part of the abstract factory pattern. We're not sure we understand why, but don't be confused if you read about it in some other source or course book. There are in fact different ideas about most of the design patterns and they all exist in various flavors and versions.

The version we present here focuses on the fact that it can produce both Weapons and Characters (but use their abstract types and never reveals the concrete types). As we showed above, it is possible to create a factory for the various concrete factories in the example. We're just not sure that the factory of factories is what makes the abstract factory pattern deserve its name. We hope you don't get fooled or confused by the names, and rather focus on trying to understand how the various examples work, what they try to accomplish and what the benefits are from decoupling the creation of objects from the client code.

All three versions of the factory pattern we show you here, aims to protect client code from changes when new concrete classes are added, or when existing concrete classes change their constructors. The factories are supposed to deal with such changes, leaving the client code unaffected.

Exercises

This chapter comes with a github repository (see links below). The repository has three folders:

  • simple-factory
  • factory-method
  • abstract-factory

The simple-factory and factory-method folders contain two folders:

  • before
  • after

Your task is to look at the "before" folders, and try to change the files there to use the pattern in question. The suggested solution is in the "after" folder.

For instance, start with simple-factory and look at the "before" folder. Make a new directory where you change the code in "before" to use the simple factory pattern. For hints, look at the code in the simple-factory/after folder.

The abstract-factory folder is not part of the exercises - it is the code examples from this page and the lecture.

You may use your solution from factory-method and evolve it to use the abstract-factory pattern if you want to. For inspiration, see the code in the abstract-factory folder, and revisit the slides for abstract-factory and read the explanation and texts on this page again.

Chapter links

Videos

Source code

Books this chapter is a part of

Further reading

Book TOC | previous chapter | next chapter