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.