Metoda wytwórcza – Wzorzec Projektowy (Factory Method)

Metoda wytwórcza (ang. Factory Method) – wzorzec projektowy. Przykład zastosowania oparty na historii w celu łatwiejszego zrozumienia tego wzorca dla laika. Przed przystąpieniem do czytania tego wpisu zalecam przeczytać wpis o Fabryce Abstrakcyjnej.

James miał firmę, która zajmowała się składaniem i sprzedawaniem hulajnóg. Hulajnoga składa się z czterech elementów: kierownicy, kolek, hamulca i naklejki. Elementy do hulajnóg James zamawiał u dwóch różnych producentów: polskiego i chińskiego (aby zrozumieć dlaczego zastosowane jest dziedziczenie zapraszam do przeczytania wpisu o Fabryce Abstrakcyjnej).

public abstract class Kolka
{
	public Color Kolor { get; set; }
}

public abstract class Kierownica
{
	public int Wysokosc { get; set; }
}

public abstract class Hamulce
{
	public int ZywotnoscWMiesiacach { get; set; }
}

public abstract class Naklejka
{
	public bool Hologram { get; set; }
}

Klasy polskich produktów:

public class PolskieKolka : Kolka
{ }

public class PolskaKierownica : Kierownica
{ }

public class PolskieHamulce : Hamulce
{ }

public class PolskaNaklejka : Naklejka
{ }

Klasy chińskich produktów.

public class ChinskieKolka : Kolka
{ }

public class ChinskaKierownica : Kierownica
{ }

public class ChinskieHamulce : Hamulce
{ }

public class ChinskaNaklejka : Naklejka
{ }

Na początku istnienia firmy, aby zbudować jedną hulajnogę wszystkie części musiały pochodzić od jednego producenta, na przykład polskiego.

public class PolskaFabryka
{
	public Kolka WyprodukujKolka()
	{
		var kolka = new PolskieKolka();
		kolka.Kolor = Color.Gold;
		return kolka;
	}

	public Kierownica WyprodukujKierownice()
	{
		var kierownica = new PolskaKierownica();
		kierownica.Wysokosc = 130;
		return kierownica;
	}

	public Hamulce WyprodukujHamulce()
	{
		var hamulce = new PolskieHamulce();
		hamulce.ZywotnoscWMiesiacach = 12;
		return hamulce;
	}

	public Naklejka WyprodukujNaklejke()
	{
		var naklejka = new PolskaNaklejka();
		naklejka.Hologram = true;
		return naklejka;
	}
}

Lub chińskiego.

public class ChinskaFabryka
{
	public Kolka WyprodukujKolka()
	{
		var kolka = new ChinskieKolka();
		kolka.Kolor = Color.Black;
		return kolka;
	}

	public Kierownica WyprodukujKierownice()
	{
		var kierownica = new ChinskaKierownica();
		kierownica.Wysokosc = 80;
		return kierownica;
	}

	public Hamulce WyprodukujHamulce()
	{
		var hamulce = new ChinskieHamulce();
		hamulce.ZywotnoscWMiesiacach = 3;
		return hamulce;
	}

	public Naklejka WyprodukujNaklejke()
	{
		var naklejka = new ChinskaNaklejka();
		naklejka.Hologram = false;
		return naklejka;
	}
}

Sama hulajnoga wyglądała w następujący sposób:

public class Hulajnoga
{
	private Kierownica _kierownica;
	private Naklejka _naklejka;
	private Hamulce _hamulce;
	private Kolka _kolka;

	public Hulajnoga(Kolka kolka, Hamulce hamulce, 
					Naklejka naklejka, Kierownica kierownica)
	{
		_kolka = kolka;
		_hamulce = hamulce;
		_naklejka = naklejka;
		_kierownica = kierownica;
	}
}

Budowa polskiej hulajnogi nie była skomplikowana:

static void Main(string[] args)
{
	var polskaFabryka = new PolskaFabryka();
	var polskieKolka = polskaFabryka.WyprodukujKolka();
	var polskieHamulce = polskaFabryka.WyprodukujHamulce();
	var polskaNaklejka = polskaFabryka.WyprodukujNaklejke();
	var polskaKierownica = polskaFabryka.WyprodukujKierownice();

	var hulajnoga = new Hulajnoga(polskieKolka, polskieHamulce, 
					polskaNaklejka, polskaKierownica);
}

Jednak wraz z rozwojem firmy James znalazł rozwiązanie, które pozwalało budować hulajnogi z części które pochodziły od różnych producentów. Jak to robił James?

static void Main(string[] args)
{
	var polskaFabryka = new PolskaFabryka();
	var polskieKolka = polskaFabryka.WyprodukujKolka();
	var polskieHamulce = polskaFabryka.WyprodukujHamulce();
	var chinskaFabryka = new ChinskaFabryka();
	var chinskaNaklejka = chinskaFabryka.WyprodukujNaklejke();
	var chinskaKierownica = chinskaFabryka.WyprodukujKierownice();

	var hulajnoga = new Hulajnoga(polskieKolka, polskieHamulce, 
					chinskaNaklejka, chinskaKierownica);
}

Po pewnym czasie okazało się, że ludzie uwielbiają sami komponować własne hulajnogi. James zauważył, że aby biznes szybciej się rozwijał to warto dać możliwość klientom, aby sami wybierali części do budowy swoich hulajnóg od różnych producentów. James rozszerzył możliwość wybierania części o innych producentów: włoskiego, szwajcarskiego lub norweskiego. James znalazł firmy, które produkowały poszczególne części: WloskaFabryka, SzwajcarskaFabryka, NoweskaFabryka. W tym momencie części mogły być sprowadzane z 5 różnych fabryk: polskiej, chińskiej, włoskiej, szwajcarskiej, norweskiej. Powiedzmy, że przychodzi klient i mówi: „chcę polskie kółka, szwajcarską kierownice, włoskie hamulce i chińską naklejkę.” Jak James zamawiał te elementy ? Jak powstawała hulajnoga?

static void Main(string[] args)
{
	var polskaFabryka = new PolskaFabryka();
	var polskieKolka = polskaFabryka.WyprodukujKolka();
	var szwajcarskaFabryka = new SzwajcarskaFabryka();
	var szwajcarskiaKierownica = szwajcarskaFabryka.WyprodukujKierownice();
	var wloskaFabryka = new WloskaFabryka();
	var wloskieHamulce = wloskaFabryka.WyprodukujHamulce();
	var chinskaFabryka = new ChinskaFabryka();
	var chinskaNaklejka = chinskaFabryka.WyprodukujNaklejke();

	var hulajnoga = new Hulajnoga(polskieKolka, wloskieHamulce, 
					chinskaNaklejka, szwajcarskiaKierownica );
}

Po chwili przyjdzie inny klient i zażyczy sobie norweską kierownicę i hamulce, polskie kółka i włoską naklejkę.
I ponownie James musi zwrócić się do odpowiedniej fabryki z prośba o konkretny element. O błąd tutaj nie jest trudno.

Metoda Wytwórcza – Refactoring

Nagle James przypomniał sobie, że kiedyś dostał ulotkę od firmy „KreatorKolek”. Firma ta jest wyspecjalizowana w tworzeniu kółek różnego pochodzenia. James skontaktował się z tą firmą, aby zrozumieć jak ona działa. Firma KreatorKolek zaprosiła James do swojej hali produkcyjnej, aby mógł zobaczyć jak to wszystko wygląda.

public class KreatorKolek
{
	public Kolka StworzKolka(string typKolka)
	{
		Kolka kolka = null;
		switch (typKolka)
		{
			case "polskie": kolka = new PolskieKolka();
				break;
			case "chinskie": kolka = new ChinskieKolka();
				break;
			case "wloskie": kolka = new WloskieKolka();
				break;
			case "szwajcarskie": kolka = new SzwajcarskieKolka();
				break;
			case "norweskie": kolka = new NorweskieKolka();
				break;
		}
		return kolka;
	}
}

Genialne! A jak James może zamówić na przykład polskie kółka?

var kreatorKolek = new KreatorKolek();
var kolka = kreatorKolek.StworzKolka("polskie");

James pomyślał, że to znakomite rozwiązanie. Nie musi kontaktować z rożnymi firmami w celu zamówienia jednej konkretnej części. Wystarczy, że skontaktuje się z KreatoremKolek i otrzyma kółko jakie chce. James pomyślał, ze skoro istnieje firma KreatorKolek to na pewną istnieją również firmy KreatorHamulcow, KreatorNaklejek oraz KreatorKierownic. James nie mylił się.

public class KreatorNaklejek
{
	public Naklejka StworzNaklejki(string typNaklejki)
	{
		Naklejki naklejka = null;
		switch (typNaklejki)
		{
			case "polskie": naklejka = new PolskieNaklejki();
				break;
			case "chinskie": naklejka = new ChinskieNaklejki();
				break;
			case "wloskie": naklejka= new WloskieNaklejki();
				break;
			case "szwajcarskie": naklejka = new SzwajcarskieNaklejki();
				break;
			case "norweskie": naklejka= new NorweskieNaklejki();
				break;
		}
		return naklejka;
	}
}

Zamawianie działo tak samo jak w przypadku KreatoraKolek:

var kreatorNaklejek = new KreatorNaklejek();
var naklejki = kreatorNaklejek.StworzNaklejki("polskie");

Pozostałe dwie firmy: KreatorHamulcow oraz KreatorKierownic działały w identyczny sposób.
Więc jak można zamówić elementy do hulajnogi i ją złożyć?

static void Main(string[] args)
{
	var kreatorKolek = new KreatorKolek();
	var kolka = kreatorKolek.StworzKolka("polskie");

	var kreatorNaklejek = new KreatorNaklejek();
	var naklejka = kreatorNaklejek.StworzNaklejke("chinskie");

	var kreatorHamulcow = new KreatorHamulcow();
	var hamulce = kreatorHamulcow.StworzHamulce("wloskie");

	var kreatorKierowic = new KreatorKierownic();
	var kierownica = kreatorKierowic.StworzKierowice("szwajcarska");

	var hulajnoga = new Hulajnoga(kolka, hamulce, naklejka, kierownica);
}

Jeżeli w katalogu pojawią się na elementy produkty z innych państw to w łatwy sposób można je dodać do programu.

public class KreatorNaklejek
{
	public Naklejka StworzNaklejki(string typNaklejki)
	{
		Naklejki naklejka = null;
		switch (typNaklejki)
		{
			case "polskie": naklejka = new PolskieNaklejki();
				break;
			case "chinskie": naklejka = new ChinskieNaklejki();
				break;
			case "wloskie": naklejka= new WloskieNaklejki();
				break;
			case "szwajcarskie": naklejka = new SzwajcarskieNaklejki();
				break;
			case "norweskie": naklejka= new NorweskieNaklejki();
				break;
			case "irlandzie": naklejka= new IrlandzkieNaklejki();
				break;
			case "niemieckie": naklejka= new NiemieckieNaklejki();
				break;
		}
		return naklejka;
	}
}

Oczywiście trzeba będzie również stworzyć odpowiednie klasy dla IrlandzkieNaklejki i NiemieckieNaklejki.

public class IrlandzkieNaklejki : Naklejka
{ }

public class NiemieckieNaklejki : Naklejka
{ }

Co jeszcze można zmienić? Zamiast używać stringów jako argumentów do metod „StworzCośTam()” można zrobić bardziej eleganckie rozwiązanie jakim jest enumerator.

public enum KrajPochodzeniaProduktu
{
	Polska,
	Chiny,
	Niemcy, 
	Wlochy,
	Szwajcaria,
	Norwegia,
	Irlandia
}

Poprawmy kod na przykład metody StworzNaklejki().

public Naklejka StworzNaklejke(KrajPochodzeniaProduktu krajPochodzeniaProduktu)
{
	Naklejka naklejka = null;
	switch (krajPochodzeniaProduktu)
	{
		case KrajPochodzeniaProduktu.Chiny:
			naklejka = new ChinskaNaklejka();
			break;
		case KrajPochodzeniaProduktu.Irlandia:
			naklejka = new IrlandzkaNaklejka();
			break;
		case KrajPochodzeniaProduktu.Niemcy:
			naklejka = new NiemieckaNaklejka();
			break;
		case KrajPochodzeniaProduktu.Norwegia:
			naklejka = new NorewskaNaklejka();
			break;
		case KrajPochodzeniaProduktu.Polska:
			naklejka = new PolskaNaklejka();
			break;
		case KrajPochodzeniaProduktu.Szwajcaria:
			naklejka = new SzwajcarskaNaklejka();
			break;
		case KrajPochodzeniaProduktu.Wlochy:
			naklejka = new WloskaNaklejka();
			break;
	}
	return naklejka;
}

Zobaczmy jak teraz można zamawiać produkty i stworzyć z nich hulajnogę.

var kreatorKolek = new KreatorKolek();
var kolka = kreatorKolek.StworzKolka(KrajPochodzeniaProduktu.Polska);

var kreatorNaklejek = new KreatorNaklejek();
var naklejka = kreatorNaklejek.StworzNaklejke(KrajPochodzeniaProduktu.Chiny);

var kreatorHamulcow = new KreatorHamulcow();
var hamulce = kreatorHamulcow.StworzHamulce(KrajPochodzeniaProduktu.Wlochy);

var kreatorKierowic = new KreatorKierownic();
var kierownica = kreatorKierowic.StworzKierowice(KrajPochodzeniaProduktu.Szwajcaria);

var hulajnoga = new Hulajnoga(kolka, hamulce, naklejka, kierownica);

Zapomnieliśmy o Jamesie. Jamesie jako klasie w programie. James jest klientem firm KreatorKolek, KreatorNaklejek, KreatorHamulców i KreatorKierownic.

public class Klient
{
	private Kolka _kolka;
	private Hamulce _hamulce;
	private Kierownica _kierownica;
	private Naklejka _naklejka;

	public void ZamowKolka(KrajPochodzeniaProduktu krajPochodzeniaProduktu)
	{
		var kreatorKolek = new KreatorKolek();
		_kolka = kreatorKolek.StworzKolka(krajPochodzeniaProduktu);
	}

	public void ZamowHamulce(KrajPochodzeniaProduktu krajPochodzeniaProduktu)
	{
		var kreatorHamulcow = new KreatorHamulcow();
		_hamulce = kreatorHamulcow.StworzHamulce(krajPochodzeniaProduktu);
	}
	public void ZamowKierownice(KrajPochodzeniaProduktu krajPochodzeniaProduktu)
	{

		var kreatorKierowic = new KreatorKierownic();
		_kierownica = kreatorKierowic.StworzKierowice(krajPochodzeniaProduktu);
	}
	public void ZamowNaklejke(KrajPochodzeniaProduktu krajPochodzeniaProduktu)
	{
		var kreatorNaklejek = new KreatorNaklejek();
		_naklejka = kreatorNaklejek.StworzNaklejke(krajPochodzeniaProduktu);
	}

	public Hulajnoga StworzHulajnoge()
	{
	  return new Hulajnoga(kolka, hamulce, naklejka, kierownica);
	}
}

I na koniec sprawdźmy czy działa nasz program i się kompiluje.

class Program
{
	static void Main(string[] args)
	{
		var James = new Klient();
		James.ZamowHamulce(KrajPochodzeniaProduktu.Wlochy);
		James.ZamowKierownice(KrajPochodzeniaProduktu.Szwajcaria);
		James.ZamowKolka(KrajPochodzeniaProduktu.Polska);
		James.ZamowNaklejke(KrajPochodzeniaProduktu.Chiny);
		var hulajnoga = James.StworzHulajnoge();
	}
}

Metoda Wytwórcza – trochę teorii:

Metoda wytwórcza
Źródło: msdn.microsoft.com/

Odnieśmy ten diagram UML do naszego przykładu.

Klient jako Client
KreatorKolek jako Creator
Kolka jako IProduct
PoslskieKolka jako ProductA
ChinskieKolka jako ProductB

Diagram nie do końca oddaje tego co jest w kodzie. Po pierwsze zamiast interfejsu mamy po prostu klasę abstrakcyjna. Moglibyśmy mieć również ProductC (IrlandzkieKolka), ProductD (NiemieckieKolka) itd. Diagram rozpatruje tylko przykład kolek.
Wzorzec ten został opisany przez Bandę Czworga (Gang of Four). Wzorzec projektowy Metoda Abstrakcyjna należy do wzorców kreacyjnych. Wzorzec ten zajmuje się tworzeniem wyspecjalizowanych instancji produktów.
Jaka różnica pomiędzy fabryką abstrakcyjną, a metodą wytwórczą? Fabryka abstrakcyjna tworzy obiekty pochodzące z jednej rodziny, np. tylko polskie produkty (kółka, naklejki, kierownica, hamulce). Fabryka abstrakcyjna ma kilka metod które tworzą różne instancje obiektów. Metoda wytwórcza zajmuje się tworzeniem tylko jednego obiektu. Klasa metody wytwórczej ma tylko jedna metodę, która zwraca jeden konkretny obiekt. Dodatkowo w fabryce abstrakcyjnej ConcreteFactory1, ConcreteFactory2 (klasy, które tworzą obiekty) dziedziczą po klasie AbstactFactory. W metodzie wytwórczej klasa Creator (powiedzmy taki odpowiednik ConcreteFactory) nie dziedziczy po żadnym interfejsie czy klasie.

Metoda Wytwórcza – przydatne linki:

DoFactory – przykład (ang)
CodeProject – przykład (ang)
MSDN – przykład (ang)
CodeProject – różnice pomiędzy Fabryką Abstrakcyjną, a Metodą Wytwórczą (ang)
StackOverflow – różnice pomiędzy Fabryką Abstrakcyjną, a Metodą Wytwórczą 1
StackOverflow – różnice pomiędzy Fabryką Abstrakcyjną, a Metodą Wytwórczą 2

3 thoughts on “Metoda wytwórcza – Wzorzec Projektowy (Factory Method)”

  1. Paweł Maślak

    Widzę jedną niezgodność. Deklarując sam element w kreatorze danego elementu np. var kolka = new PolskieKolka(); wiemy ze element jest tylko Polski i nic więcej. Właściwości elementów precyzuje się dopiero w fabryce więc, należałoby w poszczególnych kreatorach utworzyć nową instancję każdej fabryki i w zależności od danego kraju wyprodukować element zgodnie z daną fabryką. Wtedy przy zamówieniu przez klienta elementy otrzymają poprawne właściwości i nie będą puste.

    A więc:

    case: (CountryOfOrigin.Poland)
    PolishFactory polishFactory = new PolishFactory();
    kolka = polishFactory.WyprodukujKolka();
    break;

  2. Czym się różnią polskie kółka od chińskich? 🙂

    Ja metodę wytwórczą rozumiem w ten sposób, że jest to metoda przez której nadpisanie definiuje nasz obiekt. Wtedy zamiast w pięciu miejscach używać używać Rower( new Kolka(„Made in Poland”), new Kierownica(„Made in Poland”)); zrobimy new PolskiRower(). Wg, mnie chodzi wyodrębnienie konkretnych typów by nie bawić się zawsze tworzenie wszystkich składowych. Wydaje mi się, że to tak działa. Pomyśl o tym i daj znać co o tym myślisz.

    //Abstract Creator
    abstract class Rower {
    private Kolka kolka; // Abstrkcyjny Produkt
    private Kierownica kierownica; // Abstrkcyjny Produkt

    //metoda wytwórcza
    abstract protected void zmontuj();

    };

    class PolskiRower extends Rower {

    //metoda wytwórcza
    protected void getKolka() {
    this.kolka = new Kolka(„Made in Poland”); // Konkretny Produkt
    this.kierownica = new Kierownica(„Made in Poland”) /// Konkretny Produkt
    }
    };

    Popatrz jak to jest tłumaczone tutaj:
    https://stackoverflow.com/questions/5739611/differences-between-abstract-factory-pattern-and-factory-pattern/5740080#5740080

Leave a Comment

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *