The abstract factory pattern

The abstract factory pattern provides an interface for creating related objects, while hiding the choice of their concrete classes in implementations of that interface.

The power of an abstract factory comes from the fact that we can use it to make interchangeable factories in order to separate the implementation details of the created objects from their usage – an application might work on a Button, for example, with a ButtonFactory providing a SquareButton or a RoundButton, depending on its concrete implementation (SquareWidgetsFactory or RoundWidgetsFactory).

Usage example

Let’s assume we’re making a game in which aliens are invading Earth. It’s going to have varied environments, i.e. one level will take place on Earth, while another – in space.
Each of these environments is going to need its own types of enemies: Earth levels should provide ground troops (aliens with pistols and plasma guns) and in space one would expect to see various spaceships. Additionally, we want to have enemies of varying difficulty.

We’re going to create an abstract factory to provide us two types of enemies – Easy Enemies and Hard Enemies.

First, we create an interface that every easy enemy in the game is going to implement:

public interface EasyEnemy {
    public void shoot();
}

And then one for every hard enemy:

public interface HardEnemy {
    public void shootAndEvade();
}

We create our enemy classes, implementing the EasyEnemy and HardEnemy interfaces:

public class PistolAlien implements EasyEnemy {
    public void shoot() {
        System.out.println("I'm an alien shooting a pistol");
    }
}

public class PlasmaGunAlien implements HardEnemy {
    public void shootAndEvade() {
        System.out.println("I'm an alien shooting a plasma gun");
    }
}

public class LaserSpaceship implements EasyEnemy {
    public void shoot() {
        System.out.println("I'm a spaceship shooting lasers");
    }
}

public class RocketSpaceship implements HardEnemy {
    public void shootAndEvade() {
        System.out.println("I'm a spaceship shooting rockets");
    }
}

Next, we need an interface for our enemy factories (the key to the abstract factory pattern):

public interface EnemyFactory {
    EasyEnemy makeEasyEnemy();
    HardEnemy makeHardEnemy();
}

We use this interface for an Alien Factory:

public class AlienFactory implements EnemyFactory {
    public EasyEnemy makeEasyEnemy() {
        return new PistolAlien();
    }

    public HardEnemy makeHardEnemy() {
        return new PlasmaGunAlien();
    }
}

And a Spaceship Factory:

public class SpaceshipFactory implements EnemyFactory {
    public EasyEnemy makeEasyEnemy() {
        return new LaserSpaceship();
    }

    public HardEnemy makeHardEnemy() {
        return new RocketSpaceship();
    }
}

Finally, we glue it all together in our application:

public class AbstractFactoryExample {
    public static void main(String[] args) {
        makeEnemiesAndShoot(new SpaceshipFactory());
        makeEnemiesAndShoot(new AlienFactory());
    }

    private static void makeEnemiesAndShoot(final EnemyFactory enemyFactory) {
        EasyEnemy easyEnemy = enemyFactory.makeEasyEnemy();
        HardEnemy hardEnemy = enemyFactory.makeHardEnemy();
        easyEnemy.shoot();
        hardEnemy.shootAndEvade();
    }

}

The makeEnemiesAndShoot(...)method takes an EnemyFactory object and uses it to create instances of EasyEnemy and HardEnemy, which then shoot() and shootAndEvade(). This way, we can pass it a SpaceshipFactory (and have the game create spaceships) or an AlienFactory (and have the game create ground troops), with the code working on an abstract factory, giving us much flexibility.

Other use cases

It’s a useful pattern whenever you encounter related components with interchangeable implementations, such as widgets. We could use an abstract factory to create form widgets based on a theme. We could have an abstract widget factory and use concrete implementations of it for each theme that we use in our system.

Daniel Frąk Written by:

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *