Obiekt tworzony w fabryce

Programista: „Hmm, nie wiem jak się tworzy samochody. Factory stwórz mi samochód.”  Factory: „Zrobione!”

Co omówimy?

 Implementacje wzorca Factory

  • Simple Factory » – tworzymy klasę Factory, która bezpośrednio tworzy obiekty. Odpowiedzialność za tworzenie obiektów jest po stronie tej klasy. Dodawanie, modyfikowanie, utrzymanie kodu jest dość trudne.
  • Factory Method » – tworzymy interfejs nadrzędny, który pozwala decydować klasom podrzędnym o tworzeniu obiektu. Odpowiedzialność za tworzenie obiektów jest po stronie klas podrzędnych.
  • Abstract Factory » – zwraca wiele różnych powiązanych ze sobą obiektów. Taka fabryka wielu fabryk. W praktyce mamy interfejs, który posiada jeden lub wiele Factory-Methods.
Jeśli lubisz książki to polecam przeczytać:  Rusz Głową – Wzorce Projektowe

WZORCE PROJEKTOWE

Jak mówi Martin Fowler „Wzorce to tylko półprodukty – trzeba je dokończyć, aby wprowadzić je do swojego kodu.”

Wzorce są tylko szkieletami sprytnych mechanizmów. Abstrakcyjne pojęcia ułatwiające manipulację nad obiektami. Naszym zadaniem jest adaptacja tych konceptów do aplikacji. Wzorce redukują złożoność poprzez wprowadzenie gotowych już abstrakcji. Ponadto przenoszą rozmowę między developerami na wyższy poziom abstrakcji. Mówiąc, że zastanawiasz się nad zastosowaniem wzorca X przekazujesz w rzeczywistości bardzo wiele informacji. A to właśnie przekazywanie abstrakcyjnych informacji pozwoliło nam Homo Sapiens wyróżnić się na tle innych zwierząt. Oczywiście jest to skuteczne o ile oboje znacie koncepcje wzorców. Wzorce pozwolą ci zredukować złożoność twojego systemu. Ułatwią nowym osobom wejście do projektu. Zmniejszą ilość błędów poprzez zastosowanie uporządkowanych rozwiązań. Dadzą ci pakiet rozwiązań do typowego problemu. A do tego ułatwi komunikację z innymi programistami.

Ehh… Brzmi zbyt pięknie, co nie? Czasami są przypadki kiedy lekko wepchniesz, dopasujesz wzorzec do swojego kodu. Jest to okej jeśli poprawiasz tym czytelność kodu. Niemniej stosuj to z umiarem. Nie dąż do uzyskania ‚idealnego’ wzorca, bo to może tylko zwiększyć złożoność systemu. Kolejną pułapką jakiej powinieneś się wystrzegać jest POKUSA wpychania na chama wzorców tam gdzie ich nie potrzeba. Wzorce to cenne narzędzie do walki ze złożonością, ale trzeba używać z rozwagą jak wszystkiego innego! : )

FACTORY

Wzorzec Factory pozwala oddelegować tworzenie obiektu do innych klas. Jest to przydatne w momencie gdy mamy do stworzenia obiekt, który jest powiązany z wieloma innymi obiektami. Weź przykład gdzie masz 13 zależności. Czy będziesz szukać każdej z nich za każdym razem jak chcesz skorzystać z obiektu? Nie. Tutaj może przydać się koncepcja tego wzorca. Polowanie na każdą zależność może być problematyczna, dlatego dodając trochę więcej złożoności do kodu, ułatwiamy finalne tworzenie oczekiwanego obiektu.

 

Czym jest i jak nam pomoże koncepcja Factory? 

Kojarzysz na pewno grę, w której był statek kosmiczny i asteroidy. Celem było przetrwać jak najdłużej. Wyobraź sobie tworzenie takiej gry. Musisz stworzyć pojazd. Potem kolejne poziomy trudności. Każdy poziom ma więcej asteroid. Liczba ta progresywnie się powiększa. Poziomu trudności rośnie. Asteroidy mogłyby być tworzone w Factory. Poziom trudności mógłby być tworzony dynamicznie na podstawie podanych parametrów. Cała logika jest ukryta (enkapsulowana) wewnątrz naszej klasy tworzącej poziom.

A nie mogę po prostu new Factory, po co mi ta cała dodatkowa złożoność? 

Pomyśl w ten sposób. W obiektowym programowaniu mamy całą masę obiektów. Czasami, aby stworzyć jeden potrzebujemy kilku innych.  Nie chce nam się polować na każdy kolejny obiekt zamiast tego mamy Factory. Tworzymy strukturę raz, następnie używamy jej ile tylko chcemy. Kolejnym plusem jest to, że można obiekt stworzyć podczas run-time, czyli wtedy kiedy nasza aplikacja już działa. Pozornie wydaje się, że zastępujesz jedną linijkę kodu drugą. Podstawiasz jedno pod drugie. Często inicjalizacja obiektu jest bardziej skomplikowana. W większości przypadków będziesz potrzebować wielu innych obiektów do osiągnięcia celu.

Dlaczego warto używać Factory? 

  • ponowne użycie – decyzja o tworzeniu obiektu jest oddelegowana do innej klasy. Nie musimy przejmować się wszystkimi zależnościami. Tworzenie obiektu dzieje się za kulisami.
  • rozszerzalność – w momencie, gdy potrzebujemy nowej funkcjonalności po prostu tworzymy kolejną konkretną klasę. Dzięki temu nie naruszamy zasady Open/Closed, która mówi o tym, żeby tworzyć modyfikacje bez naruszania już istniejących funkcji. Zwiększa to stabilność systemu oraz zmniejsza niechciane bóle głowy : )

W większości przypadków raczej nie będzie ci potrzebny. Często nie jest wymagane abstrahowanie i ukrywanie struktury tworzenia obiektu. Z reguły wystarczy zwykły konstruktor i nie ma sensu dodawać kolejnej złożoności do projektu.

Kiedy używać Factory?

  • kiedy tworzysz kosztowny obiekt i potrzebujesz szybkiego sposobu na używanie go wielokrotnie.
  • kiedy masz zależność od zewnętrznego systemu, o którym nie masz zbyt dużego pojęcia.
  • kiedy tworzenie obiektu jest na tyle skomplikowane, że nie wystarczy zwykły konstruktor.

Zalety używania Factory?

  • zapewnia podejście interfejsowe zamiast konkretnych implementacji, czyli tworzymy strukturę aplikacji na podstawie interfejsów. Jest to usunięcie zależności pomiędzy implementacją, a konkretnym zastosowaniem.
  • zapewnia bardziej elastyczne podejście, gdyż zmiana OceanAnimalConcreteFactory nie spowoduje zmiany działania LandAnimalConcreteFactory. Kod tutaj »
  • zapewnia większą czytelność kodu gdyż każdorazowe wywołanie konstruktora new AnimalFactory niekoniecznie będzie czytelne. Podczas rozwoju aplikacji będą dodawane coraz to kolejne parametry do powyższego konstruktora i będzie się to stawało coraz bardziej nieczytelne.

Wady używania Factory?

  • abstrakcja jest zaletą jak i wadą. Ukrycie abstrakcji za abstrakcją może okazać się trudne do czytania.

 

Simple Factory

Ogólna Zasada Tworzenia Simple Factory

// SUPER-CLASS - ogólny typ, wybierający podobne cechy, zachowania...
public interface Animal { ... }

// SUB-CLASSES - konkretne implementacje Animal. Każda może być tworzona w inny sposób.
public class Dog implements Animal { ... }
public class Dinosaur implements Animal { ... } 
public class Salmon implements Animal { ... }

--------------------------------------------------------------------------------

public enum AnimalType { LAND, OCEAN }

// SimpleFactory - stworzenie konkretnej implementacji na podstawie parametrów
public class EarthAnimalsFactory
    public Animal createAnimal(AnimalType type) { ... }

-------------------------------------------------------------------------------- 


new EarthAnimalsFactory().createAnimal( AnimalType.OCEAN )

 

Factory Method

Ogólna Zasada Tworzenia Factory Method

// SUPER-CLASS - ogólny typ
public interface Animal { ... }

// SUB-CLASSES - konkretne implementacje, każda jest tworzona w inny sposób.
public class Dog implements Animal { ... }
public class Dinosaur implements Animal { ... } 
public class Salmon implements Animal { ... }


--------------------------------------------------------------------------------

// SUPER-CLASS - ogólny typ Factory. Podobnie jak w Animal.
public interface AnimalFactory 
    public Animal createAnimal()
// SUB-CLASS - konkretne implementacje - metoda szablonowa (czyli niezmienna część algorytmu jaki tworzymy) 
public class LandAnimalConcreteFactory { public Animal createAnimal() { ... } }
public class OceanAnimalConcreteFactory { public Animal createAnimal() { ... } }

-------------------------------------------------------------------------------- 

new OceanAnimalConcreteFactory().getAnimal()
new LandAnimalConcreteFactory().getAnimal()

 

Abstract Factory

Ogólna Zasada Tworzenia Abstract Factory

// SUPER-CLASS - ogólny typ 
public interface Animal { ... }

// SUB-CLASSES - konkretne implementacje, każda jest tworzona w inny sposób
public class Dog implements Animal { ... }
public class Dinosaur implements Animal { ... } 
public class Salmon implements Animal { ... }


--------------------------------------------------------------------------------

//SUPER-CLASS - ogólny typ Factory. Podobnie jak w Animal. 
public interface AnimalsAbstractFactory {  
    public Animal createLandAnimal();
    public Animal createOceanAnimal();
} 

// SUB-CLASS - konkretne implementacje - tworzenie rodziny obiektów na zasadzie kompozycji
public class EarthAnimalsConcreteFactory {  
    public Animal getLandAnimals() { ... }
    public Animal getOceanAnimals() { ... }
}

public class MarsAnimalsConcreteFactory { ... } 

-------------------------------------------------------------------------------- 

new EarthAnimalsConcreteFactory().getLandAnimals();
new EarthAnimalsConcreteFactory().getOceanAnimals();

 

MATERIAŁY:

ŹRÓDŁA:

Zdjęcia główne autorstwa: Thomas Hafeneth