Encapsulating creation
Although only the Simple Factory Method is a true singleton, you’ll find that each specify
factory class in the more general types of factories will only have a single instance.
- Simple Factory method
One approach is to make the factory a static method of the base class:
//: factory:shapefact1:ShapeFactory1.java
// A simple static factory method.
package factory.shapefact1;
import java.util.*;
import junit.framework.*;
abstract class Shape {
public abstract void draw();
public abstract void erase();
public static Shape factory(String type) {
if(type.equals("Circle")) return new Circle();
if(type.equals("Square")) return new Square();
throw new RuntimeException(
"Bad shape creation: " + type);
}
}
class Circle extends Shape {Circle() {} // Package-access constructor
public void draw() {
System.out.println("Circle.draw");
}
public void erase() {
System.out.println("Circle.erase");
}
}
class Square extends Shape {
Square() {} // Package-access constructor
public void draw() {
System.out.println("Square.draw");
}
public void erase() {
System.out.println("Square.erase");
}
}
public class ShapeFactory1 extends TestCase {
String shlist[] = { "Circle", "Square",
"Square", "Circle", "Circle", "Square" };
List shapes = new ArrayList();
public void test() {
Iterator it = Arrays.asList(shlist).iterator();
while(it.hasNext())
shapes.add(Shape.factory((String)it.next()));
it = shapes.iterator();
while(it.hasNext()) {
Shape s = (Shape)it.next();
s.draw();
s.erase();
}
}
public static void main(String args[]) {
junit.textui.TestRunner.run(ShapeFactory1.class);
}
} ///:~
To encourage creation to only happen in the factory( ), the constructors for the specific
types of Shape are give package access, so factory( ) has access to the constructors but they
are not available outside the package.
- Polymorphic factories
different types of factories can be subclassed from the basic factory,for example
interface Shape {
void draw();
void erase();
}
abstract class ShapeFactory {
protected abstract Shape create();
private static Map factories = new HashMap();
public static void
addFactory(String id, ShapeFactory f) {
factories.put(id, f);
}
// A Template Method:
public static final
Shape createShape(String id) {
if(!factories.containsKey(id)) {
try {
// Load dynamically
Class.forName("factory.shapefact2." + id);
} catch(ClassNotFoundException e) {
throw new RuntimeException(
"Bad shape creation: " + id);
}
// See if it was put in:
if(!factories.containsKey(id))
throw new RuntimeException(
"Bad shape creation: " + id);
}
return
((ShapeFactory)factories.get(id)).create();
}
}
class Circle implements Shape {
private Circle() {}
public void draw() {
System.out.println("Circle.draw");
}
public void erase() {
System.out.println("Circle.erase");
}
private static class Factory
extends ShapeFactory {
protected Shape create() {
return new Circle();
}
}
static {
ShapeFactory.addFactory(
"Circle", new Factory());
}
}
.......
- Abstract factories
The Abstract Factory pattern looks like the factory objects we’ve seen previously, with not
one but several factory methods. Each of the factory methods creates a different kind of
object. The idea is that at the point of creation of the factory object, you decide how all the
objects created by that factory will be used.
As another example suppose you are creating a general-purpose gaming environment and you
want to be able to support different types of games
interface Obstacle {
void action();
}
interface Player {
void interactWith(Obstacle o);
}
class Kitty implements Player {
public void interactWith(Obstacle ob) {
System.out.print("Kitty has encountered a ");
ob.action();
}
}
class KungFuGuy implements Player {
public void interactWith(Obstacle ob) {
System.out.print("KungFuGuy now battles a ");
ob.action();
}
}
class Puzzle implements Obstacle {
public void action() {
System.out.println("Puzzle");
}
}
class NastyWeapon implements Obstacle {
public void action() {
System.out.println("NastyWeapon");
}
}
// The Abstract Factory:
interface GameElementFactory {Player makePlayer();
Obstacle makeObstacle();
}
// Concrete factories:
class KittiesAndPuzzles
implements GameElementFactory {
public Player makePlayer() {
return new Kitty();
}
public Obstacle makeObstacle() {
return new Puzzle();
}
}
class KillAndDismember
implements GameElementFactory {
public Player makePlayer() {
return new KungFuGuy();
}
public Obstacle makeObstacle() {
return new NastyWeapon();
}
}
class GameEnvironment {
private GameElementFactory gef;
private Player p;
private Obstacle ob;
public GameEnvironment(
GameElementFactory factory) {
gef = factory;
p = factory.makePlayer();
ob = factory.makeObstacle();
}
public void play() { p.interactWith(ob); }
}
public class Games extends TestCase {
GameElementFactory
kp = new KittiesAndPuzzles(),
kd = new KillAndDismember();
GameEnvironment
g1 = new GameEnvironment(kp),
g2 = new GameEnvironment(kd);
// These just ensure no exceptions are thrown:
public void test1() { g1.play(); }
public void test2() { g2.play(); }
public static void main(String args[]) {
junit.textui.TestRunner.run(Games.class);
}
} ///:~
In this environment, Player objects interact with Obstacle objects, but there are different
types of players and obstacles depending on what kind of game you’re playing. You determine
the kind of game by choosing a particular GameElementFactory, and then the
GameEnvironment controls the setup and play of the game. In this example, the setup and
play is very simple, but those activities (the initial conditions and the state change) can
determine much of the game’s outcome. Here, GameEnvironment is not designed to be
inherited, although it could very possibly make sense to do that.