Wzorzec typu fabryka to wzorzec z rodziny wzorców kreacyjnych. Rozróżniamy dwie odmiany tego wzorca: metoda fabrykująca oraz fabryka abstrakcyjna. Obie te odmiany służą do uproszczenia tworzenia nowych obiektów.
Metoda fabrykująca (ang. factory method) – w tym typie wzorca tworzymy nowe obiekty bez ujawniania klientowi logiki ich tworzenia. Wzorzec zapewnia interfejs lub klasę abstrakcyjną do tworzenia nowych obiektów w ramach klasy bazowej, lecz jednocześnie pozwala klasom podrzędnym na zmienienie typu tworzonego obiektu.
Stwórzmy program będący fabryką mebli. Niech istnieją dwa typy mebli, które chcemy wyprodukować: sofa i fotel. W tym celu utwórzmy klasę abstrakcyjną „Article”, po której będą dziedziczyć klasy „Sofa” i „Armchair”. Stwórzmy też dla nich typ enum który będzie zawierał dwa stany: „Sofa” i „Armchair”. Następnie zaimplementujmy naszą fabrykę tworząc klasę abstrakcyjną „Factory”, po której będzie dziedziczyć nasza fabryka artykułów meblarskich – „ArticleFactory”. Klasa ta będzie nadpisywać metodę „createArticle”, która z kolei przyjmuje jako argument stworzony wcześniej stan w typie wyliczeniowym. W ten sposób w naszej głównej klasie main możemy utworzyć instancję klasy „Factory”, a później na niej wywoływać metodę tworzącą konkretne już artykuły. Klasa ta nie ma dostępu do klas konkretnych typów artykułów, ponieważ ich konstruktory mają dostęp domyślny – czyli taki, dzięki któremu tylko klasy z tej samej paczki mogą tworzyć obiekty tych klas.
Poniżej zaprezentowany został schemat działania metody fabrykującej oraz kod obrazujący tworzenie poszczególnych klas i wywołanie fabryki w metodzie mainowej.
Article.java
package factory.example.articles;
public abstract class Article {
private String style;
private String material;
private String color;
protected Article(String style, String material, String color) {
this.style = style;
this.material = material;
this.color = color;
}
public String getStyle() {
return style;
}
public String getMaterial() {
return material;
}
public String getColor() {
return color;
}
}
Armchair.java
package factory.example.articles;
public class Armchair extends Article{
Armchair(String style, String material, String color) {
super(style, material, color);
}
}
Sofa.java
package factory.example.articles;
public class Sofa extends Article{
Sofa(String style, String material, String color) {
super(style, material, color);
}
}
ArticleType.java
package factory.example.articles;
public enum ArticleType {
SOFA,
ARMCHAIR
}
Factory.java
package factory.example.articles;
abstract public class Factory {
abstract public Article createArticle(ArticleType type);
}
ArticleFactory.java
package factory.example.articles;
public class ArticleFactory extends Factory{
@Override
public Article createArticle(ArticleType type) {
switch(type) {
case SOFA:
return new Sofa("modern", "leather", "beige");
case ARMCHAIR:
return new Armchair("modern", "leather", "oatmeal");
default:
throw new UnsupportedOperationException("No such type");
}
}
}
Main.java
package factory.example;
import factory.example.articles.*;
public class Main {
public static void main(String[] args) {
Factory factory = new ArticleFactory();
Article mySofa = factory.createArticle(ArticleType.SOFA);
Article myArmchair = factory.createArticle(ArticleType.ARMCHAIR);
}
}
Fabryka abstrakcyjna (ang. abstract factory) – jest czymś w rodzaju super-fabryki, która tworzy inne fabryki. Ten typ wzorca jest też nazywany fabryką fabryk. We wzorcu tym interfejs jest odpowiedzialny za tworzenie fabryki powiązanych obiektów bez jawnego sprecyzowania ich klas. Każda wygenerowana fabryka może przekazywać obiekty zgodne ze wzorcem Fabryka.
Wyobraźmy sobie, że w naszej fabryce mebli chcemy wytwarzać artykuły w różnych stylach, na przykład w stylu nowoczesnym oraz wiktoriańskim oraz w dwóch gamach kolorystycznych – jasnej i ciemnej. Utwórzmy typ wyliczeniowy enum dla gam kolorystycznych, które występują dla każdego artykułu. Jasny i ciemny odcień dla sofy oraz fotela, nazwijmy te klasy odpowiednio „SofaColor” oraz „ArmchairColor”. Utwórzmy także typ wyliczeniowy enum dla naszych dwóch styli: nowoczesnego i wiktoriańskiego. Zmodyfikujmy teraz klasę Article, tak aby przyjmowała pole typu „Style”, nazwijmy je „style”. Wszystkie pliki umieśćmy w paczce „articles”. Teraz zaimplementujmy interfejs „Factory”, w paczce, w której znajduje się główna metoda main. Będzie on posiadał dwie metody tworzące odpowiednie artykuły w określonych kolorach. Następnie stwórzmy fabryki abstrakcyjne, które będą implementować interfejs „Factory” i będą tworzyć artykuły w określonym stylu: „VictorianFactory” i „ModernFactory”. Teraz możemy już przejść do naszej głównej metody main i utworzyć w niej obiekty naszych dwóch rodzajów fabryk. Załóżmy, że otrzymaliśmy zamówienie na komplet mebli nowoczensych w jasnej gamie kolorystycznej oraz komplet mebli wiktoriańskich w ciemnej gamie. Tworzymy więc nowe artykuły, nazwijmy je odpowiednio „sofa” i „armchair”, za pośrednictwem naszych fabryk abstrakcyjnych. Kolor wybieramy z klasy enum Color. Tak więc utworzyliśmy dwa komplety mebli, których komponenty pasują do siebie stylem i gamą kolorystyczną.
Article.java
package factory.example.articles;
public abstract class Article {
private Style style;
private String material;
private String color;
protected Article(Style style, String material, String color) {
this.style = style;
this.material = material;
this.color = color;
}
public Style getStyle() {
return style;
}
}
Armchair.java
package factory.example.articles;
public class Armchair extends Article{
public Armchair(Style style, String material, String color) {
super(style, material, color);
}
}
Sofa.java
package factory.example.articles;
public class Sofa extends Article{
public Sofa(Style style, String material, String color) {
super(style, material, color);
}
}
AmchairColor.java
package factory.example.articles;
public enum ArmchairColor {
BEIGE,
BLACK
}
SofaColor.java
package factory.example.articles;
public enum SofaColor {
OATMEAL,
BLACK
}
Style.java
package factory.example.articles;
public enum Style {
VICTORIAN,
MODERN
}
Factory.java
package factory.example;
import factory.example.articles.ArmchairColor;
import factory.example.articles.Article;
import factory.example.articles.SofaColor;
public interface Factory {
Article createSofa(SofaColor color);
Article createArmchair(ArmchairColor color);
}
ModernFactory.java
package factory.example;
import factory.example.articles.*;
public class ModernFactory implements Factory{
Style style = Style.MODERN;
public Article createSofa(SofaColor color) {
switch(color) {
case OATMEAL:
return new Sofa(style, "leather", "oatmeal");
case BLACK:
return new Sofa(style,"leather", "black");
default:
throw new IllegalArgumentException("Unknown model");
}
}
public Article createArmchair(ArmchairColor color) {
switch(color) {
case BEIGE:
return new Armchair(style, "leather", "beige");
case BLACK:
return new Armchair(style, "leather", "black");
default:
throw new IllegalArgumentException("Unknown model");
}
}
}
VictorianFactory.java
package factory.example;
import factory.example.articles.*;
public class VictorianFactory implements Factory{
Style style = Style.VICTORIAN;
public Article createSofa(SofaColor color) {
switch(color) {
case OATMEAL:
return new Sofa(style, "leather", "oatmeal");
case BLACK:
return new Sofa(style, "leather", "black");
default:
throw new IllegalArgumentException("Unknown model");
}
}
public Article createArmchair(ArmchairColor color) {
switch(color) {
case BEIGE:
return new Armchair(style,"leather", "beige");
case BLACK:
return new Armchair(style, "leather", "black");
default:
throw new IllegalArgumentException("Unknown model");
}
}
}
Main.java
package factory.example;
import factory.example.articles.ArmchairColor;
import factory.example.articles.Article;
import factory.example.articles.SofaColor;
public class Main {
public static void main(String[] args) {
Factory modernFactory = new ModernFactory();
Factory victorianFactory = new VictorianFactory();
Article sofa = modernFactory.createSofa(SofaColor.OATMEAL);
Article armchair = modernFactory.createArmchair(ArmchairColor.BEIGE);
System.out.println(sofa.getStyle());
System.out.println(armchair.getStyle());
Article sofa2 = victorianFactory.createSofa(SofaColor.BLACK);
Article armchair2 = victorianFactory.createArmchair(ArmchairColor.BLACK);
System.out.println(sofa2.getStyle());
System.out.println(armchair2.getStyle());
}
}
PODSUMOWANIE
Wzorzec fabryka jest bardzo popularnym wzorcem, który posiada dwa bardzo ważne elementy: enkapsuluje tworzenie nowych obiektów oraz wprowadza abstrakcję. Do jego zalet należy uniknięcie ścisłego związku z kodem w klasie menadżerskiej, trzymanie się zasady jednej odpowiedzialności tj. umieszczenie kodu tworzącego nowe obiekty w jednym miejscu, co pomaga w utrzymaniu kodu. Do zalet należy także zgodność z zasadą otwarte-zamknięte, czyli możliwość wprowadzania kodu dla nowych wariantów produktów bez psucia istniejącego już kodu klienckiego. Wadą natomiast może być większe skomplikowanie, gdyż wprowadzenie tego wzorca wymaga utworzenia nowych interfejsów i klas, co może niedoświadczonemu programiście przysporzyć problemów w zrozumieniu czytanego kodu.