
Fabryka
Wzorzec projektowy fabryka (ang. factory pattern) należy do wzorców kreacyjnych. W tym artykule pokrótce przedstawię dwie odmiany tego wzorca – metodę fabrykującą oraz fabrykę abstrakcyjną. Oba te podejścia służą do uproszczenia tworzenia nowych obiektów.
Metoda fabrykująca
Metoda fabrykująca pozwala decydować klasom podrzędnym jakiego typu obiekt będzie utworzony.
Przykład kodu bez użycia wzorca projektowego: Tworzymy cztery klasy (Pajak, Szkieletor, Harpia, Demon), które będą rozszerzały klasę “Stwor”.
Wyobraźmy sobie, że tworzymy grę, w której walczymy z różnego rodzaju stworami, aby nasza postać była coraz mocniejsza.
Klasa Stwor:
package stwory;
public abstract class Stwor {
private String nazwa;
private int doswiadczenie;
private int zycie;
private int obrazenia;
public Stwor(String nazwa, int doswiadczenie, int zycie, int obrazenia) {
this.nazwa = nazwa;
this.doswiadczenie = doswiadczenie;
this.zycie = zycie;
this.obrazenia = obrazenia;
}
public String getNazwa() {
return nazwa;
}
public int getDoswiadczenie() {
return doswiadczenie;
}
public int getZycie() {
return zycie;
}
public int getObrazenia() {
return obrazenia;
}
}
Klasa Pajak:
package stwory;
public class Pajak extends Stwor {
public Pajak(String nazwa, int doswiadczenie, int zycie, int obrazenia) {
super(nazwa, doswiadczenie, zycie, obrazenia);
}
}
Klasa Szkieletor:
package stwory;
public class Szkieletor extends Stwor {
public Szkieletor(String nazwa, int doswiadczenie, int zycie, int obrazenia) {
super(nazwa, doswiadczenie, zycie, obrazenia);
}
}
Klasa Harpia:
package stwory;
public class Harpia extends Stwor {
public Harpia(String nazwa, int doswiadczenie, int zycie, int obrazenia) {
super(nazwa, doswiadczenie, zycie, obrazenia);
}
}
Klasa Demon:
package stwory;
public class Demon extends Stwor {
public Demon(String nazwa, int doswiadczenie, int zycie, int obrazenia) {
super(nazwa, doswiadczenie, zycie, obrazenia);
}
}
Klasa Main:
import stwory.*;
public class Main {
public static void main(String[] args) {
Stwor demon = new Demon("Demon", 1000, 1500, 800);
Stwor pajak = new Pajak("Pająk", 130, 200, 111);
Stwor harpia = new Harpia("Harpia", 800, 1200, 700);
Stwor szkieletor = new Szkieletor("Szkieletor", 500, 700, 500);
}
}
W klasie main można zauważyć, że to od użytkownika zależy jakie konkretnie parametry przyjmie każdy rodzaj stwora, ponieważ wszystkie są tworzone od podstaw. Dodatkowo gra powinna posiadać co najmniej kilkanaście rodzajów przeciwników dla naszej postaci. Aby uniknąć wielu pomyłek podczas tworzenia kolejnych klas stworów i ich parametrów z pomocą przychodzi nam wzorzec Fabryka, a dokładniej Metoda Fabrykująca. Nie chcemy przecież, aby przypadkiem np. “Pająk” był mocniejszy od “Demona”.
Wygląd kodu po implementacji metody fabrykującej (pominę tę część kodu, gdzie nie wprowadzono zmian).
Klasa Fabryka:
package stwory;
public abstract class Fabryka {
abstract public Stwor tworzStwora(TypStwora typStwora);
}
Enum TypStwora:
package stwory;
public enum TypStwora {
HARPIA, DEMON, PAJAK, SZKIELETOR
}
Klasa FabrykaStworow:
package stwory;
public class FabrykaStworow extends Fabryka {
@Override
public Stwor tworzStwora(TypStwora typStwora) {
switch (typStwora) {
case HARPIA:
return new Harpia("Harpia", 800, 1200, 700);
case PAJAK:
return new Pajak("Pająk", 130, 200, 111);
case SZKIELETOR:
return new Szkieletor("Szkieletor", 500, 700, 500);
case DEMON:
return new Demon("Demon", 1000, 1500, 800);
default:
throw new UnsupportedOperationException("Nie ma takiego typu.");
}
}
}
Klasa Main:
import stwory.*;
public class Main {
public static void main(String[] args) {
Fabryka fabryka = new FabrykaStworow();
Stwor demon = fabryka.tworzStwora(TypStwora.DEMON);
Stwor pajak = fabryka.tworzStwora(TypStwora.PAJAK);
Stwor harpia = fabryka.tworzStwora(TypStwora.HARPIA);
Stwor szkieletor = fabryka.tworzStwora(TypStwora.SZKIELETOR);
}
}
Można zauważyć, że implementacja wzorca metody fabrykującej jest dość prosta. W naszym przypadku dodaliśmy klasę abstrakcyjną “Fabryka”, gdzie tworzymy metodę “tworzStwora()”, która przyjmuje jako argument enum “TypStwora”. Następnie tworzymy kolejną klasę – fabrykę konkretną – “FabrykaStworow”, która rozszerza klasę “Fabryka” oraz nadpisuje jej metodę. Dzięki zaimplementowaniu tego wzorca możemy w prosty sposób tworzyć nowe obiekty bez pomocy operatora “new” oraz nie musimy się martwić, że pomylimy którykolwiek parametr.
Dodatkowo dzięki przeniesieniu wszystkich klas poza Main do jednej paczki i zmianie widoczności konstruktorów w klasach konkretnych obiektów (public -> domyślny) oraz klasie “Stwor” (public -> protected) sprawiliśmy, że w klasie Main konstruktor nie może zostać wywołany spoza paczki, a nowe jednostki mogą być tworzone jedynie z poziomu fabryki.
Fabryka abstrakcyjna
Fabryka abstrakcyjna pozwala na tworzenie różnych obiektów jednego typu bez określania ich konkretnych klas.
Przyjmijmy, że nasza gra się powiększa i jedna klasa abstrakcyjna “Stwor” to za mało. Rozbijemy ją na klasę “StworLadowy” oraz “StworLatajacy”. Obie klasy będą różniły się tylko nazwą. Dodatkowo jeżeli posiadamy dwie klasy abstrakcyjne to potrzebujemy również dwie osobne fabryki abstrakcyjne oraz dwie implementacje tych fabryk. Dlatego stworzymy abstrakcyjne “FabrykaLadowa” oraz “FabrykaLatajaca”, a także ich implementacje “FabrykaStworowLadowych” i “FabrykaStworowLatajacych”. Naszym kolejnym celem jest stworzenie z naszych stworów ich mocniejsze odpowiedniki, tzw. Elity.
Dlatego nasze fabryki konkretnych jednostek musimy rozbić na kolejne dwie klasy. W ten sposób powstanie nam “FabrykaZwyklychStworowLadowych” – “FabrykaElityStworowLadowych” oraz “FabrykaZwyklychStworowLatajacych” – “FabrykaElityStworowLatajacych”. Teraz, jeśli byśmy chcieli dołożyć kolejny rodzaj stworów, np. stwory wodne to trzeba stworzyć kolejną fabrykę abstrakcyjną oraz jej dwie implementacje. W takim przypadku nasza sytuacja staje się mocno skomplikowana. Na szczęście możemy wykorzystać tutaj wzorzec Fabryki Abstrakcyjnej, który pomoże nam uporządkować tę sytuację.
Lista stworzonych klas:

Implementację wzorca fabryki abstrakcyjnej zaczniemy od usunięcia klas “FabrykaZwyklychStworowLadowych”, “FabrykaZwyklychStworowLatajacych”, “FabrykaElityStworowLadowych”, “FabrykaElityStworowLatajacych”, “FabrykaLatajaca”. Klasę “FabrykaLadowa” zmienimy po prostu na “Fabryka”. Następnie tworzymy klasy “FabrykaZwykla”, “FabrykaElit”, gdzie będziemy tworzyć stwory zwykłe oraz elity.
Wygląd kodu po implementacji wzorca:
Lista klas:

Klasa Fabryka:
package stwory;
public abstract class Fabryka {
abstract public StworLadowy tworzStworaLadowego(TypStwora typStwora);
abstract public StworLatajacy tworzStworaLatajacego(TypStwora typStwora);
}
Klasa FabrykaZwykla:
package stwory;
public class FabrykaZwykla extends Fabryka {
@Override
public StworLadowy tworzStworaLadowego(TypStwora typStwora) {
switch (typStwora) {
case PAJAK:
return new Pajak("Pająk", 130, 200, 111);
case SZKIELETOR:
return new Szkieletor("Szkieletor", 500, 700, 500);
default:
throw new UnsupportedOperationException("Nie ma takiego typu.");
}
}
@Override
public StworLatajacy tworzStworaLatajacego(TypStwora typStwora) {
switch (typStwora) {
case HARPIA:
return new Harpia("Harpia", 800, 1200, 700);
case DEMON:
return new Demon("Demon", 1000, 1500, 800);
default:
throw new UnsupportedOperationException("Nie ma takiego typu.");
}
}
}
Klasa FabrykaElit:
package stwory;
public class FabrykaElit extends Fabryka {
@Override
public StworLadowy tworzStworaLadowego(TypStwora typStwora) {
switch (typStwora) {
case PAJAK:
return new Pajak("Pająk (Elita)", 260, 400, 222);
case SZKIELETOR:
return new Szkieletor("Szkieletor (Elita)", 1000, 1400, 1000);
default:
throw new UnsupportedOperationException("Nie ma takiego typu.");
}
}
@Override
public StworLatajacy tworzStworaLatajacego(TypStwora typStwora) {
switch (typStwora) {
case HARPIA:
return new Harpia("Harpia (Elita)", 1600, 2400, 1400);
case DEMON:
return new Demon("Demon (Elita)", 2000, 3000, 1600);
default:
throw new UnsupportedOperationException("Nie ma takiego typu.");
}
}
}
Klasa Main:
import stwory.*;
public class Main {
public static void main(String[] args) {
Fabryka fabrykaZwykla = new FabrykaZwykla();
Fabryka fabrykaElit = new FabrykaElit();
StworLadowy pajak = fabrykaZwykla.tworzStworaLadowego(TypStwora.PAJAK);
StworLadowy szkieletor = fabrykaZwykla.tworzStworaLadowego(TypStwora.SZKIELETOR);
StworLatajacy harpia = fabrykaZwykla.tworzStworaLatajacego(TypStwora.HARPIA);
StworLatajacy demon = fabrykaZwykla.tworzStworaLatajacego(TypStwora.DEMON);
StworLadowy pajakElita = fabrykaElit.tworzStworaLadowego(TypStwora.PAJAK);
StworLadowy szkieletorElita = fabrykaElit.tworzStworaLadowego(TypStwora.SZKIELETOR);
StworLatajacy harpiaElita = fabrykaElit.tworzStworaLatajacego(TypStwora.HARPIA);
StworLatajacy demonElita = fabrykaElit.tworzStworaLatajacego(TypStwora.DEMON);
}
}
Teraz, aby dodać kolejny typ stworów musimy:
- Utworzyć abstrakcyjną klasę z nowym typem stwora i dodać statystyki.
- Następnie tworzymy klasę z konkretnym przedstawicielem nowego typu, która rozszerza wcześniej utworzoną klasę abstrakcyjną.
- Do klasy Fabryka dodajemy metodę odpowiednią dla nowego typu stworów.
- Do klasy TypStwora dodajemy nowy typ.
- Implementujemy nową metodę w klasie FabrykaZwykla oraz FabrykaElit.
- W klasie Main wywołujemy komendę rozpoczynającą produkcję stworów nowego typu.