Wzorzec Fabryka to popularna technika, która pozwala na stworzenie kodu uzależnionego od abstrakcji. Polega na oddelegowaniu tworzenia obiektu do innych klas. Wzorzec zapewnia hermetyzację procesu tworzenia obiektów, pozwala na oddzielnie kodu klienta od implementacji oraz realizuje regułę odwracania zależności (DIP – Dependency Inversion Principle). Wzorzec występuje w dwóch wariantach: Metoda Fabrykująca (Factory Method) i Fabryka Abstrakcyjna (Abstract Method).
Wzorzec Metoda Fabrykująca – opiera się na wykorzystaniu mechanizmu dziedziczenia. Polega na zdefiniowaniu interfejsu pozwalającego na tworzenie obiektów oraz na przeniesienie odpowiedzialności za tworzenie obiektów do klas podrzędnych, które implementują metodę fabrykującą. Wszystkie inne metody zaimplementowane w abstrakcyjnej klasie Fabryki operują na obiektach stworzonych przed metodę fabrykującą. Klasa Fabryka posiada implementacje wszystkich potrzebnych metod poza metodą wytwarzającą produkty (metodą fabrykującą). Metodę Fabrykującą muszą implementować wszystkie klasy podrzędne fabryki (fabryki rzeczywiste).

Żeby lepiej zobrazować sobie funkcjonowanie tego wzorca przeanalizujmy przykład. Załóżmy, że mamy dwie rodzaje piekarni. Pierwsza niech zajmuje się wytwarzaniem pieczywa tradycyjnego, druga pieczywa włoskiego. Tak wyglądałby kod bez użycia wzorca:
public class PiekarniaBezWzorca {
public Pieczywo utworzPieczywo(String styl, String typ){
Pieczywo pieczywo = null;
if (styl.equals("Tradycyjne")){
if (typ.equals("Chleb pszenny")){
pieczywo = new TradycyjnyChlebPszenny();
} else if (typ.equals("Chleb żytni")){
pieczywo = new TradycyjnyChlebZytni();
}
} else if (styl.equals("Włoskie")){
if (typ.equals("Ciabatta")){
pieczywo = new WłoskieCiabatta();
} else if (typ.equals("Focaccia")){
pieczywo = new WłoskieFocaccia();
}
}
pieczywo.przygotowanie();
pieczywo.pieczenie();
pieczywo.pakowanie();
return pieczywo;
}
}
W podanej wersji wszystkie obiekty Pieczywo są tworzone wewnątrz klasy. Klasa PiekarniaBezWzorca jest uzależniona od wszystkich obiektów, które są przez nią bezpośrednio tworzone.
Tak będzie wyglądać kod po zastosowaniu wzorca Metoda Fabrykująca i oddelegowaniu tworzenia obiektu Pieczywo to odpowiedniej podrzędnej klasy – PiekarniaWłoska lub PiekarniaTradycyjna.

public abstract class Piekarnia {
public Pieczywo zlecPieczywo(String typ){
Pieczywo pieczywo;
pieczywo = utworzPieczywo(typ);
pieczywo.przygotowanie();
pieczywo.pieczenie();
pieczywo.pakowanie();
return pieczywo;
}
abstract Pieczywo utworzPieczywo(String typ);
}
public class PiekarniaWłoska extends Piekarnia{
@Override
Pieczywo utworzPieczywo(String typ) {
if (typ.equals("Ciabatta")){
return new WłoskieCiabatta();
} else if (typ.equals("Focaccia")){
return new WłoskieFocaccia();
} else return null;
}
}
public class PiekarniaTradycyjna extends Piekarnia{
@Override
Pieczywo utworzPieczywo(String typ) {
if (typ.equals("Chleb pszenny")){
return new TradycyjnyChlebPszenny();
} else if (typ.equals("Chleb żytni")){
return new TradycyjnyChlebZytni();
} else return null;
}
}
Wzorzec Fabryka Abstrakcyjna – polega na dostarczeniu interfejsu do tworzenia rodzin obiektów (które są ze sobą spokrewnione lub od siebie zależne). Klasa abstrakcyjna definiuje interfejs, który musi być zaimplementowany przez wszystkie fabryki rzeczywiste. Fabryki rzeczywiste implementują różne rodziny produktów.
Powyższy wzorzec został wykorzystany do stworzenia fabryki produktów do poszczególnych piekarni.

Poniżej znajduję się kod dla fabryki składników pieczywa włoskiego:
public interface FabrykaSkladnikowPieczywa {
public Mąka utworzMaka();
public Zakwas utworzZakwas();
}
public class FabrykaSkladnikowPieczywaWloskie implements FabrykaSkladnikowPieczywa {
@Override
public Mąka utworzMaka() {
return new MąkaWłoska();
}
@Override
public Zakwas utworzZakwas() {
return new ZakwasWloski();
}
}
Fabrykę składników możemy połączyć razem z piekarnią, aby piekarnia używała odpowiednich składników dla danego typu piekarni. Spójrzmy na kod zaimplementowany do piekarni włoskiej. Po dodaniu kilku aktualizacji: metody abstrakcyjnej w klasie Pieczywo oraz dodaniu odpowiedniej fabryki składników otrzymujemy następujący kod:
public class PiekarniaWłoska extends Piekarnia {
@Override
Pieczywo utworzPieczywo(String typ) {
Pieczywo pieczywo = null;
FabrykaSkladnikowPieczywa fabrykaSkladnikow = new FabrykaSkladnikowPieczywaWloskie();
if (typ.equals("Ciabatta")){
pieczywo = new WłoskieCiabatta(fabrykaSkladnikow);
pieczywo.ustawNazwePieczywa("Ciabatta");
} else if (typ.equals("Focaccia")){
pieczywo = new WłoskieFocaccia(fabrykaSkladnikow);
pieczywo.ustawNazwePieczywa("Focaccia");
}
return pieczywo;
}
}
public class WłoskieFocaccia extends Pieczywo {
FabrykaSkladnikowPieczywa fabrykaSkladnikow;
public WłoskieFocaccia(FabrykaSkladnikowPieczywa fabrykaSkladnikow){
this.fabrykaSkladnikow = fabrykaSkladnikow;
}
@Override
public void przygotowanie() {
System.out.println("W przygotowaniu: " + nazwaPieczywa);
mąka = fabrykaSkladnikow.utworzMaka();
zakwas = fabrykaSkladnikow.utworzZakwas();
System.out.println("Użyto mąki: " + mąka.pobierzNazwe());
System.out.println("Użyto zakwasu: " + zakwas.pobierzNazwe());
}
}
public class WłoskieCiabatta extends Pieczywo {
FabrykaSkladnikowPieczywa fabrykaSkladnikow;
public WłoskieCiabatta(FabrykaSkladnikowPieczywa fabrykaSkladnikow){
this.fabrykaSkladnikow = fabrykaSkladnikow;
}
@Override
public void przygotowanie() {
System.out.println("W przygotowaniu: " + nazwaPieczywa);
mąka = fabrykaSkladnikow.utworzMaka();
zakwas = fabrykaSkladnikow.utworzZakwas();
System.out.println("Użyto mąki: " + mąka.pobierzNazwe());
System.out.println("Użyto zakwasu: " + zakwas.pobierzNazwe());
}
}
public class Main {
public static void main(String[] args) {
Piekarnia piekarnia = new PiekarniaWłoska();
piekarnia.zlecPieczywo("Ciabatta");
System.out.println("-------------------");
piekarnia.zlecPieczywo("Focaccia");
}
}
Rezultatem testu jest:
W przygotowaniu: Ciabatta
Użyto mąki: mąka włoska
Użyto zakwasu: zakwas włoski
Pieczenie
Pakowanie
——————-
W przygotowaniu: Focaccia
Użyto mąki: mąka włoska
Użyto zakwasu: zakwas włoski
Pieczenie
Pakowanie
