Najprawdopodobniej do tego momentu spotkałeś się już ze wzorcami projektowymi. Na przykład z singletonem ( singleton ).

Przypomnijmy sobie, czym są wzorce, dlaczego są potrzebne, czym są wzorce generatywne (do których należy samotnik) i przestudiujmy nowy wzorzec – metodę fabryczną.

Wzorzec projektowy lub wzorzec w tworzeniu oprogramowania to powtarzalna konstrukcja architektoniczna, która reprezentuje rozwiązanie problemu projektowego w pewnym powtarzającym się kontekście .

Zazwyczaj szablon nie jest kompletnym szablonem, który można bezpośrednio przekonwertować na kod, jest to tylko przykład rozwiązania problemu, który można wykorzystać w różnych sytuacjach.

Wzorce tworzenia to wzorce projektowe, które dotyczą procesu tworzenia obiektów . Pozwalają uniezależnić system od sposobu tworzenia, komponowania i prezentacji obiektów.

Metoda fabryczna to generatywny wzorzec projektowy, który definiuje wspólny interfejs do tworzenia obiektów w klasie nadrzędnej, zapewniając możliwość tworzenia tych samych obiektów dla jej spadkobierców . W czasie tworzenia potomkowie mogą określić, którą klasę utworzyć.

Jaki problem rozwiązuje wzór?

Wyobraź sobie, że postanawiasz stworzyć program dostaw. Początkowo wynajmiesz kurierów z samochodami i użyjesz obiektu jako pojazdu dostawczego w programieSamochód. Kurierzy dostarczają przesyłki z punktu A do punktu B, C itd. Wszystko jest proste.

Program zyskuje na popularności, Twój biznes się rozwija, chcesz wejść na nowe rynki. Możesz więc na przykład dodatkowo zacząć dostarczać jedzenie i zajmować się transportem towarowym. Wtedy żywność może być dostarczana przez kurierów pieszych, a także na skuterach i rowerach, a do potrzeb ładunkowych potrzebne są ciężarówki.

Teraz ważne jest, abyś wiedział, kiedy, do kogo, co i ile dokładnie zostanie dostarczone, biorąc pod uwagę, ile każdy kurier może przewieźć lub przewieźć. Nowe typy pojazdów mają różne prędkości i pojemności. Wtedy przekonasz się, że większość podmiotów w programie jest silnie powiązana z obiektem.Samochód, a aby Twój program działał z innymi metodami dostawy, będziesz musiał przepisać istniejącą bazę kodów i powtarzać to za każdym razem dla każdego nowego transportu.

Rezultatem jest przerażający kod wypełniony instrukcjami warunkowymi, które wykonują jedną lub drugą akcję w zależności od transportu.

Rozwiązanie

Wzorzec metody fabrycznej sugeruje tworzenie obiektów nie bezpośrednio przy użyciu operatora new , ale poprzez wywołanie specjalnej metody fabrycznej. Podklasy klasy zawierającej metodę fabryczną mogą modyfikować tworzone obiekty konkretnych tworzonych pojazdów. Na pierwszy rzut oka może się to wydawać bezcelowe: po prostu przenieśliśmy wywołanie konstruktora z jednego końca programu na drugi. Ale teraz możesz zastąpić metodę fabryczną w podklasie, aby zmienić typ tworzonego transportu.

Spójrzmy na diagram klas tego podejścia:

Aby ten system działał, wszystkie zwrócone obiekty muszą mieć wspólny interfejs. Podklasy będą mogły tworzyć obiekty różnych klas korzystających z tego samego interfejsu.

Na przykład klasy Truck i Car implementują interfejs Courier Transport z metodą dostarczania . Każda z tych klas implementuje metodę w inny sposób: ciężarówki dostarczają towary, podczas gdy samochody dostarczają żywność, paczki i tak dalej. Metoda fabryczna w klasie Truck Maker zwróci obiekt ciężarówki, podczas gdy klasa Car Maker zwróci obiekt samochodu.

Dla klienta metody fabrycznej nie ma różnicy między tymi obiektami, ponieważ będzie on traktował je jako swego rodzaju abstrakcyjny transport kurierski . Będzie dla niego ważne, aby obiekt miał metodę dostarczania, ale nie jest ważne, jak dokładnie działa.

Implementacja w Javie:


public interface CourierTransport {
	void deliver();
}
public class Car implements CourierTransport {
	@Override
	public void deliver() {
    		System.out.println("The package is being delivered by car");
	}
}
public class Truck implements CourierTransport {
	@Override
	public void deliver() {
    		System.out.println("The freight is being delivered by truck");
	}
}
public abstract class CourierTransportCreator {
	public abstract CourierTransport createTransport();
}
public class CarCreator extends CourierTransportCreator {
	@Override
	public CourierTransport createTransport() {
    		return new Car();
	}
}
public class TruckCreator extends CourierTransportCreator {
	@Override
	public CourierTransport createTransport() {
    		return new Truck();
	}
}
 
public class Delivery {
	private String address;
	private CourierTransport courierTransport;
 
	public void Delivery() {
	}
 
	public Delivery(String address, CourierTransport courierTransport) {
    	this.address = address;
    	this.courierTransport = courierTransport;
	}
 
	public CourierTransport getCourierTransport() {
    		return courierTransport;
	}
 
	public void setCourierTransport(CourierTransport courierTransport) {
    		this.courierTransport = courierTransport;
	}
 
	public String getAddress() {
    		return address;
	}
 
	public void setAddress(String address) {
    		this.address = address;
	}
}
public static void main(String[] args) {
    	// Accept a new type of order from the database (pseudocode)
    	String type = database.getTypeOfDeliver();
 
    	Delivery delivery = new Delivery();
    	
    	// Set the transport for delivery
        delivery.setCourierTransport(getCourierTransportByType(type));
    	
    	// Make the delivery
        delivery.getCourierTransport().deliver();
 
	}
 
	public static CourierTransport getCourierTransportByType(String type) {
    	switch (type) {
        	case "CarDelivery":
            	return new CarCreator().createTransport();
        	case "TruckDelivery":
            	return new TruckCreator().createTransport();
        	default:
            	throw new RuntimeException();
	    }
	}
    

Jeżeli będziemy chcieli utworzyć nowy obiekt dostawy, to program automatycznie utworzy dla nas obiekt transportowy z jego typu.

Kiedy zastosować wzór?

1. Kiedy typy i zależności obiektów, z którymi Twój kod musi współpracować, nie są z góry znane.

Metoda fabryczna oddziela kod produkcyjny transportu od reszty kodu używanego przez transport. Dzięki temu kod do tworzenia obiektów można rozszerzyć bez dotykania głównego.

Tak więc, aby dodać obsługę nowego transportu, należy utworzyć nową podklasę i zdefiniować w niej metodę fabryczną, zwracając stamtąd instancję nowego transportu.

2. Gdy chcesz oszczędzać zasoby systemowe przez ponowne wykorzystanie już utworzonych obiektów zamiast tworzenia nowych.

Ten problem zwykle występuje podczas pracy z obiektami intensywnie korzystającymi z zasobów, takimi jak łączenie się z bazą danych, systemem plików itp.

Wyobraź sobie, ile kroków musisz wykonać, aby ponownie wykorzystać istniejące obiekty:

  1. Po pierwsze, powinieneś utworzyć wspólne repozytorium do przechowywania wszystkich tworzonych w nim obiektów.

  2. Prosząc o nowy przedmiot, będziesz musiał zajrzeć do magazynu i sprawdzić, czy nie ma tam nieużywanego przedmiotu.

  3. Zwróć obiekt do kodu klienta.

  4. Ale jeśli nie ma wolnych obiektów, utwórz nowy, dodając go do repozytorium.

Cały ten kod trzeba gdzieś umieścić, aby nie zapchać kodu klienta. Najdogodniejszym miejscem byłby konstruktor obiektów, ponieważ wszystkie te kontrole są potrzebne tylko podczas tworzenia obiektów. Ale niestety konstruktor zawsze tworzy nowe obiekty, nie może zwrócić istniejącej instancji.

Oznacza to, że potrzebna jest inna metoda, która zwróciłaby zarówno istniejące, jak i nowe obiekty. Będzie to metoda fabryczna.

3. Gdy chcesz umożliwić użytkownikom rozszerzanie części twojego frameworka lub biblioteki.

Użytkownicy mogą rozszerzać klasy frameworka poprzez dziedziczenie. Ale jak sprawić, by framework tworzył obiekty z tych nowych klas, a nie ze standardowych?

Rozwiązaniem jest umożliwienie użytkownikom rozszerzania nie tylko komponentów, których chcą, ale także klas, które tworzą te komponenty. W tym celu klasy tworzące muszą mieć określone metody tworzenia, które można zdefiniować.

Zalety

  • Zwalnia klasę z powiązania z określonymi klasami transportu.
  • Przechowuje kod tworzenia transportu w jednym miejscu, ułatwiając jego konserwację.
  • Upraszcza dodawanie nowych środków transportu do programu.
  • Implementuje zasadę otwarte/zamknięte.

Wady

Może prowadzić do dużych równoległych hierarchii klas, ponieważ każda klasa produktów musi mieć własną podklasę twórcy.

Podsumować

Zapoznałeś się ze wzorcem metody fabrycznej i zobaczyłeś jej możliwą implementację. Ten wzorzec jest dość często używany w różnych bibliotekach, które z kolei udostępniają obiekty do tworzenia obiektów.

Użyj wzorca metody fabrycznej, jeśli chcesz łatwo wprowadzić nowe obiekty podklasy do swojego programu w oparciu o istniejące, aby wchodzić w interakcje z główną logiką biznesową, aby nie rozrastać kodu zbytnio z powodu różnych kontekstów.