Zastosowanie
Fabryka abstrakcyjna
(ang. Abstract factory
) (wzorzec kreacyjny) dostarcza interfejs do tworzenia powiązanych bądź zależnych obiektów różnych typów bez dokładnej znajomości ich klas. Fabryka abstrakcyjna definiuje rodzinę produktów możliwych do wygenerowania z danej fabryki. Konkretne fabryki mają za zadanie zdecydować o typie i stworzyć obiekty z jednej rodziny produktów. Implementacja wzorca Fabryki abstrakcyjnej
może być utożsamiana ze zbiorem Metod wytwórczych
dla każdej abstrakcji produktu. Jednakże w przeciwieństwie do Metody wytwórczej
, której celem jest tworzenie obiektów o jednym wspólnym typie, Fabryka abstrakcyjna
realizuje zadanie tworzenia rodziny powiązanych ze sobą obiektów. Proces inicjalizacji obiektów jest centralizowany, kod otwarty na zmiany, a klient odizolowany od szczegółów implementacji rodziny produktów. Klient nie musi znać typów tworzonych produktów, ponieważ cała odpowiedzialność spoczywa na fabryce. Spełnia zasadę OCP
- otwarte/zamknięte, a także DIP
- odwrócenia zależności.
Ograniczenia
Wprowadzenie nowego abstrakcyjnego produktu do rodziny produktów wymusza rozbudowę wszystkich fabryk o metodę zwracającą kolejną abstrakcje, natomiast dodanie nowego typu pociąga za sobą modyfikację metod w niektórych fabrykach. W przypadku małych projektów użycie wzorca może być przesadnę co w rezultacie zwiększy złożoność kodu.
Użycie
Fabryka abstrakcyjna
znajduje zastosowanie tam, gdzie zachodzi potrzeba tworzenia zbioru powiązanych ze sobą produktów o wspólnych typach, ale różnej implementacji, a klient ma być zwolniony ze znajomości szczegółów implementacji. Ponadto dokładne typy obiektów nie muszą być znane w trakcie tworzenia kodu, a decyzja o tym jakie obiekty zostaną wygenerowane zapada dynamicznie.
Implementacja
Konkretna fabryka implementuje abstrakcyjną fabrykę AbstractFactory
, która deklaruje dostarczenie grupy produktów ze sobą powiązanych. Każda fabryka odpowiada za stworzenie produktów na swój ustalony sposób. Produkty rozszerzają klasę abstrakcyjną produktu AbstractProduct
lub implementują jej interfejs.
Poniższy listing przedstawia implementację fabryki abstrakcyjnej AbstractFactory
w oparciu o dwie fabrykie FactoryA
, FactoryB
i dwie rodziny produktów AbstractProduct1
, AbstractProduct2
.
public class FactoryA extend AbstractFactory {
@Override
public AbstractProduct1 createProduct1(Product1Type type) {
//set args to constructor based on type
if(type == Product1Type.BIG)
return new Product1A("BIG");
else
return new Product1A();
}
@Override
public AbstractProduct2 createProduct2(Product2Type type) {
//set args to constructor based on type
if(type == Product2Type.CIRCLE)
return new Product2A("CIRCLE");
else
return new Product2A();
}
}
public class FactoryB extend AbstractFactory {
@Override
public AbstractProduct1 createProduct1(Product1Type type) {
//set args to constructor based on type
if(type == Product1Type.SMALL)
return new Product1B("SMALL");
else
return new Product1B();
}
@Override
public AbstractProduct2 createProduct2(Product2Type type) {
//ignore args for this type
return new Product2B();
}
}
public abstract class AbstractFactory {
public abstract AbstractProduct1 createProduct1(Product1Type type);
public abstract AbstractProduct2 createProduct2(Product2Type type);
}
public abstract class AbstractProduct1 {
//some fields and methods
}
public abstract class AbstractProduct2 {
//some fields and methods
}
Klient decyduje z której fabryki skorzystać w celu wygenerowania obiektów. Obiekty można generować bezpośrednio z fabryki lub przekazać do obiektu docelowego fabrykę jako parametr konstruktora.
//check conditions and choose factory
AbstractFactory factory = new FactoryA();
CompleteProduct product = new CompleteProduct(factory);
//where CompleteProduct looks like below
public class CompleteProduct {
private AbstractProduct1 product1;
private AbstractProduct2 product2;
public CompleteProduct(AbstractFactory factory) {
product1 = factory.createProduct1();
product2 = factory.createProduct2();
}
//other methods
}
Przykład
Sklep internetowy JerseySport
prowadzi sprzedaż zestawów odzieżowych Jersey
(skarpetki Socks
, spodenki Shorts
, koszulka Shirt
) dla różnych dyscyplin sportu. Klient może skonfigurować zestawy pod względem rozmiaru, koloru oraz dla niektórych dyscyplin także długości rękawków koszulek. Ze względu na mnogość konfiguracji parametrów odzieży oraz ich przeznaczenia, sposób składania zamówienia realizowany jest poprzez wzorzec Fabryki abstrakcyjnej
. Klasa reprezentująca kompletny sportowy zestaw odzieży wraz ze strukturą poszczególnych elementów zestawu przedstawiona jest na poniższym listingu.
public class Jersey {
private Socks socks;
private Shorts shorts;
private Shirt shirt;
public Jersey(Socks socks, Shorts shorts, Shirt shirt) {
this.socks = socks;
this.shorts = shorts;
this.shirt = shirt;
}
public int getPrice() {
return socks.getPrice() + shorts.getPrice() + shirt.getPrice();
}
public String getDescription() {
return "Jersey: " + socks.getName() + " " + shorts.getName() + " " + shirt.getName();
}
public boolean isAvailable() {
return socks.isAvailable() && shorts.isAvailable() && shirt.isAvailable();
}
}
public abstract class Socks extend Clothes {
private Color color;
public Socks(Color color) {
this.color = color;
}
}
public abstract class Shorts extend Clothes {
private Color color;
private Size size;
public Shorts(Color color, Size size) {
this.color = color;
this.size = size;
}
}
public abstract class Shirt extend Clothes {
private Color color;
private Size size;
private boolean longSleeve;
public Shirt(Color color, Size size, boolean longSleeve) {
this.color = color;
this.size = size;
this.longSleeve = longSleeve;
}
}
public abstract Clothes {
public abstract int getPrice();
public abstract String getName();
public abstract boolean isAvailable();
}
Elementy zestawu odzieżowego Socks
, Shorts
, Shirt
w zależności od sportu różnią się ceną, właściwościami oraz sposobem sprawdzania dostępności. W związku z tym dla każdej dyscypliny sportowej tworzona jest dedykowana konfiguracja elementów zestawu odzieżowego. Poniższy listing przedstawia implementacje dla piłkarskiej odzieży.
public FootballSocks extend Socks {
@Override
public int getPrice() {
return 10;
}
@Override
public String getName() {
return "Footbal socks " + color.toString();
}
@Override
public boolean isAvailable() {
//connect with server database and providers in specific way and check availability
return true; //mock
}
}
public FootballShorts extend Shorts {
@Override
public int getPrice() {
return 20;
}
@Override
public String getName() {
return "Footbal shorts " + color.toString() + " " + size.toString();
}
@Override
public boolean isAvailable() {
//connect with server database and providers in specific way and check availability
return true; //mock
}
}
public FootballShirt extend Shirt {
@Override
public int getPrice() {
return 40;
}
@Override
public String getName() {
if(longSleeve)
return "Footbal shirt long sleeve " + color.toString() + " " + size.toString();
else
return "Footbal shirt " + color.toString() + " " + size.toString();
}
@Override
public boolean isAvailable() {
//connect with server database and providers in specific way and check availability
return true; //mock
}
}
//implement BasketballSocks, BasketballShorts, BasketballShirt and others sport jersey in similar way
Do składania kompletnego zestawu służą dedykowane dla każdej dyscypliny sportu fabryki (FootballFactory
, BasketballFactory
itp) implementujące abstrakcyjnę fabrykę JerseyFactory
.
public class FootballFactory extend JerseyFactory {
@Override
public Socks createSocks(Color color) {
return new FootballSocks(color);
}
@Override
public Shorts createShorts(Size size, Color color) {
return new FootballShorts(size, color);
}
@Override
public Shirt createShirt(Size size, Color color, boolean longSleeve) {
return new FootballShirt(size, color, longSleeve);
}
}
public class BasketballFactory extend JerseyFactory {
@Override
public Socks createSocks(Color color) {
return new BasketballSocks(color);
}
@Override
public Shorts createShorts(Size size, Color color) {
return new BasketballShorts(size, color);
}
@Override
public Shirt createShirt(Size size, Color color, boolean longSleeve) {
return new BasketballShirt(size, color, false);
}
}
public abstract class JerseyFactory {
public abstract Socks createSocks(Color color);
public abstract Shorts createShorts(Size size, Color color);
public abstract Shirt createShirt(Size size, Color color, boolean longSleeve);
public Jersey createJersey(Size size, Color color, boolean longSleeve) {
Socks socks = createSocks(color);
Shorts shorts = createShorts(size, color);
Shirt shirt = createShirt(size, color, longSleeve);
return new Jersey(socks, shorts, shirt);
}
}
Proces kompletowania zamówienia przez klienta może przebiegać następująco.
List<Jersey> orders = new ArrayList();
//order football jersey
JerseyFactory factory = new FootballFactory();
orders.add(factory.createJersey(Size.M, Color.RED);
orders.add(factory.createJersey(Size.L, Color.RED);
orders.add(factory.createJersey(Size.XL, Color.RED);
//order basketball jerseys
factory = new BasketballFactory();
orders.add(factory.createJersey(Size.XL, Color.BLUE);
orders.add(factory.createJersey(Size.S, Color.BLUE);
orders.add(factory.createJersey(Size.L, Color.BLUE);
//order custom basketball jersey
Socks socks = factory.createSocks(Color.WHITE);
Shorts shorts = factory.createShorts(Color.BLACK, Size.M);
Shirt shirt = factory.createShirt(Color.RED, Size.M, true);
orders.add(new Jersey(socks, shorts, shirt));
//check availability, price and get order summary before confirm order
int totalPrice = 0;
String summary;
for(Jersey jersey : orders) {
totalPrice = totalPrice + jersey.getPrice();
summary = summary + jersey.getDescription() + "\n";
if(!jersey.isAvailable()) {
//show warning about longer delivered time
}
}
//show total price and summary
Biblioteki
Ze względu na specyfikację wzorca, implementacja Fabryka abstrakcyjna
spoczywa na programiście.