Mateusz Wajnberger

Fabryka – kreacyjny wzorzec projektowy

Rozpatrzymy dwie odmiany danego wzorca projektowego: Metoda Wytwórcza (ang. Factory Method) oraz Fabryka Abstrakcyjna (ang. Abstract Factory). Jako przykład weźmiemy strategiczną grę z serii Władcy Pierścieni, gdzie tworzymy dwie jednostki: Uruk-hai ( jednostka wywodząca się z rasy Orków) oraz Jeźdzcy Rohanu (rasa Ludzka). 

Kod przed implementacją wzorca – Metoda Wytwórcza:

Tworzymy klasę abstrakcyjną „Entity”:

public abstract class Entity {

private int healthPoints;
private int damagePoints;

public Entity(int healthPoints, int damagePoints) {
this.healthPoints = healthPoints;
this.damagePoints = damagePoints;
}

public int getHealthPoints() {
return healthPoints;
}

public int getDamagePoints() {
return damagePoints;
}
}

Dwie klasy, które dziedziczą po klasie abstrakcyjnej: 

public class UrukHai extends Entity {

public UrukHai(int healthPoints, int damagePoints) {
super(healthPoints, damagePoints);
}
}

public class RiderOfRohan extends Entity {

public RiderOfRohan(int healthPoints, int damagePoints) {
super(healthPoints, damagePoints);
}
}

Od osoby tworzącej jednostki zależy jakie będą parametry danej jednostki co może doprowadzić do błędów czy też nieporozumień:

public class Main {

public static void main(String[] args) {
Entity riderOfRohan = new RiderOfRohan(100, 50);
Entity urukHai = new UrukHai(80, 40);
}
}

Implementacja – Metoda Wytwórcza 

Tworzymy klasę abstrakcyjną „Factory”, a w niej metodę która zwraca typ „Entity”: 

public abstract class Factory {

abstract public Entity createEntity(EntityType type);
}

Nastepnie tworzymy już konkretną fabrykę, która rozszerza klasę abstrakcyjną oraz Enuma, który zawiera oba typy naszych jednostek:

public enum EntityType {

RIDEROFROHAN,
URUKHAI;
}

public class EntityFactory extends Factory{

@Override
public Entity createEntity(EntityType entityType) {

switch(entityType) {
case RIDEROFROHAN:
return new RiderOfRohan(100, 50);
case URUKHAI:
return new UrukHai(80, 40);
default:
throw new UnsupportedOperationException("There is no such type");
}
}
}

Teraz, gdy chcemy utworzyć nowe jednostki to nie korzystamy z konstruktorów tylko wykorzystujemy do tego metodę fabrykującą:

public class Main {

public static void main(String[] args) {
Factory factory = new EntityFactory();
Entity riderOfRohan = factory.createEntity(EntityType.RIDEROFROHAN);
Entity urukHai = factory.createEntity(EntityType.URUKHAI);
}
}

Kod przed implementacją wzorca – Fabryka Abstrakcyjna

Zakładamy, że nie wystarczy nam już tylko jedna klasa abstrakcyjna „Entity” dlatego zastępujemy ją dwiema innymi klasami abstrakcyjnymi:  

public abstract class HumanEntity {

private int healthPoints;
private int damagePoints;

protected HumanEntity(int healthPoints, int damagePoints) {
this.healthPoints = healthPoints;
this.damagePoints = damagePoints;
}

public int getHealthPoints() {
return healthPoints;
}

public int getDamagePoints() {
return damagePoints;
}
}

public abstract class OrcEntity {

private int healthPoints;
private int damagePoints;

protected OrcEntity(int healthPoints, int damagePoints) {
this.healthPoints = healthPoints;
this.damagePoints = damagePoints;
}

public int getHealthPoints() {
return healthPoints;
}

public int getDamagePoints() {
return damagePoints;
}
}

Zabieg ten powoduje, że w tym przypadku potrzebujemy kolejne 2 fabryki abstrakcyjne (OrcFactory oraz HumanFactory). Przyjmijmy także, że w naszej grze  strategicznej mamy do czynienia z dwiema stronami konfilktu(FirstPerson oraz SecondPerson) co powoduje dodawanie kolejnych klas do naszego kodu. 

Implementacja – Fabryka Abstrakcyjna

W abstrakcyjnej klasie „Factory” tworzymy dwie metody: 

public abstract class Factory {

abstract public HumanEntity createHumanEntity(EntityType type);
abstract public OrcEntity createOrcEntity(EntityType type);
}

Tworzymy dwie fabryki po obu stronach konfliktu, które rozszerzają naszą klasę abstrakcyjną:

public class FirstPersonFactory extends Factory {
    
    @Override
    public HumanEntity createHumanEntity(EntityType type) {
        switch (type) {
            case RIDEROFROHAN:
                return new RiderOfRohan(100, 50);
            default:
                throw new UnsupportedOperationException("Unknown type");
        }
    }

    @Override
    public OrcEntity createOrcEntity(EntityType type) {
        switch (type) {
            case URUKHAI:
                return new UrukHai(80, 40);
            default:
                throw new UnsupportedOperationException("Unknown type");
        }
    }
}

public class SecondPersonFactory extends Factory {

    @Override
    public HumanEntity createHumanEntity(EntityType type) {
        switch (type) {
            case RIDEROFROHAN:
                return new RiderOfRohan(100, 50);
            default:
                throw new UnsupportedOperationException("Unknown type");
        }
    }

    @Override
    public OrcEntity createOrcEntity(EntityType type) {
        switch (type) {
            case URUKHAI:
                return new UrukHai(80, 40);
            default:
                throw new UnsupportedOperationException("Unknown type");
        }
    }
}

Teraz w celu utworzenia odpowiednich jednostek używamy poszczególne fabryki:

public class Main {

public static void main(String[] args) {
Factory firstPersonFactory = new FirstPersonFactory();
Factory secondPersonFactory = new SecondPersonFactory();

HumanEntity firstPersonRiderOfRohan = firstPersonFactory.createHumanEntity(EntityType.RIDEROFROHAN);
OrcEntity firstPersonUrukHai = firstPersonFactory.createOrcEntity(EntityType.URUKHAI);

HumanEntity secondPersonRiderOfRohan = secondPersonFactory.createHumanEntity(EntityType.RIDEROFROHAN);
OrcEntity secondPersonUrukHai = secondPersonFactory.createOrcEntity(EntityType.URUKHAI);
}
}

Podsumowanie

Omawiane wzorce mają na celu enkapsulacje procesu tworzenia nowych obiektów czyli oddelegowanie produkcji do innych klas, tak aby użytkownik musiał jedynie wywołać jedną metodę i w ten sposób otrzymał  gotowy obiekt, a co za tym idzie nie miał pojęcia o całym procesie.

mateusz.wajnberger@gmail.com