Friday, April 27, 2012

Abstract Factory design pattern tutorial

When you develop an application sometimes it must have a possibility to work with large number of different resources or in different operation systems. Of course, you need a special mechanism to do it. Also your application has to be flexible enough so you could easily add some new resources without code changing and headache. Abstract Factory pattern can effectively resolve such problem.

This post is available in Russian.

Abstract Factory


Type: creational

Purpose: to provide a contract for creating families of related or dependent objects without having to specify their concrete classes.

Alternative names: Kit, Toolkit

Implementation:
AbstractFactory - an abstract class or interface that defines the create methods for abstract products.

Product - an abstract class or interface describing the general behavior of the resource that will be
used by the application.

ConcreteFactory - a class derived from the abstract factory. It implements create methods for one or more concrete products.

ConcreteProduct - a class derived from the abstract product, providing an implementation for a specific resource or operating environment.


Imagine that we decided to create a computer game about World War II. We have two opposite countries - Germany and USSR. We have just started creating a game, so every side has only two types of enginery - tank and aircraft. Let's create all needed classes with usage of Abstract Factory pattern.

Create an interface of general enginery factory. It contains methods for tank and aircraft creation:
public interface EngineryFactory {
    
    public Tank createTank();

    public Aircraft createAircraft();
}
Created interface matches AbstractFactory interface at diagram.

Create an abstract class Tank. It describes all tank properties and has methods for reading/changing them. Also it has a self-describing method getDescription():
public abstract class Tank {
    private int speed;
    private int power;

    public int getSpeed() {
        return speed;
    }

    public void setSpeed(int speed) {
        this.speed = speed;
    }

    public int getPower() {
        return power;
    }

    public void setPower(int power) {
        this.power = power;
    }
    
    public abstract String getDescription();
}
Class Tank is our analog of class ProductA.

Analogically, create class Aircraft that represents abstract aircraft:
public abstract class Aircraft {
    int speed;
    int power;
    int altitude;

    public int getSpeed() {
        return speed;
    }

    public void setSpeed(int speed) {
        this.speed = speed;
    }

    public int getPower() {
        return power;
    }

    public void setPower(int power) {
        this.power = power;
    }

    public int getAltitude() {
        return altitude;
    }

    public void setAltitude(int altitude) {
        this.altitude = altitude;
    }

    public abstract String getDescription();
}
Class Aircraft matches class ProductB.

Now let's create concrete factory for producing concrete tanks and aircrafts. Class USSREngineryFactory implements interface EngineryFactory and allows to create only soviet enginery - "T-34" tanks and "IL-2" aircrafts:
public class USSREngineryFactory implements EngineryFactory {

    @Override
    public Tank createTank() {
        return new T34_Tank();
    }

    @Override
    public Aircraft createAircraft() {
        return new Il2_Aircraft();
    }
}
Class USSREngineryFactory matches class ConcreteFactory1.

Create class that represents concrete tank - "T-34". Override method getDescription() so that it returns tank information:
public class T34_Tank extends Tank {
    private static final String NAME = "T-34";
    private static final String COUNTRY = "USSR";

    public String getDescription() {
        return NAME + " " + COUNTRY;
    }
}
Class T34_Tank is a concrete product of USSREngineryFactory. It matches class ConcreteProductA1.

Create a class that describes "IL-2" aircraft:
public class Il2_Aircraft extends Aircraft {
    private static final String NAME = "Il-2";
    private static final String COUNTRY = "USSR";

    public String getDescription() {
        return NAME + " " + COUNTRY;
    }
}
Class Il2_Aircraft matches class ConcreteProductB1.

Analogically, create a factory that could produce german enginery - "Е-25" tanks and "Messerschmitt Bf.110" aircrafts:
public class GermanEngineryFactory implements EngineryFactory {

    @Override
    public Tank createTank() {
        return new E25_Tank();
    }

    @Override
    public Aircraft createAircraft() {
        return new MesserschmittBf110_Aircraft();
    }
}
Class GermanEngineryFactory matches class ConcreteFactory2.

Class E25_Tank matches class ConcreteProductA2:
public class E25_Tank extends Tank {
    private static final String NAME = "E-25";
    private static final String COUNTRY = "Germany";

    public String getDescription() {
        return NAME + " " + COUNTRY;
    }
}

Class MesserschmittBf110_Aircraft matches ConcreteProductB2:
public class MesserschmittBf110_Aircraft extends Aircraft {
    private static final String NAME = "Messerschmitt Bf.110";
    private static final String COUNTRY = "Germany";

    public String getDescription() {
        return NAME + " " + COUNTRY;
    }
}

Now all classes are created. Well done!

Create class AbstractFactoryDemo to demonstrate how the pattern works. Create method showEnginery() that takes factory as an argument and prints information about enginery that it could produce. Invoke that method with USSREngineryFactory and GermanEngineryFactory instances as arguments:
public class AbstractFactoryDemo {
    public static void main(String[] args) {
        EngineryFactory engineryFactory = new USSREngineryFactory();

        System.out.println("USSR enginery:");
        showEnginery(engineryFactory);
        
        System.out.println();

        System.out.println("German enginery:");
        engineryFactory = new GermanEngineryFactory();
        showEnginery(engineryFactory);
    }
    
    public static void showEnginery(EngineryFactory engineryFactory) {
        Tank tank = engineryFactory.createTank();
        System.out.println(tank.getDescription());

        Aircraft aircraft = engineryFactory.createAircraft();
        System.out.println(aircraft.getDescription());
    }
}

Run method main():










After program execution we can see description of enginery that can be produced by both countries. We can easily add new factories and replace them.

Folder structure:




















Application source code is available at http://code.google.com/p/abstract-factory-demo/.

No comments:

Post a Comment