Fabryka Abstrakcyjna – Wzorzec Projektowy (Abstract Factory)

Fabryka Abstrakcyjna (ang. Abstract Factory) – wzorzec projektowy. Przykład zastosowania oparty na historii w celu łatwiejszego zrozumienia tego wzorca dla laika.

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. Firma podzespoły do hulajnóg kupowała u chińskiego producenta. Chiński producent części miał fabrykę, która specjalizowała się w wytwarzaniu produktów tylko do hulajnóg.

public class ChinskieKolka
{
	public Color Kolor { get; set; }
}

public class ChinskaKierownica 
{
	public int Wysokosc { get; set; }
}

public class ChinskieHamulce
{
	public int ZywotnoscWMiesiacach { get; set; }
}

public class ChinskaNaklejka
{
	public bool Hologram { get; set; }
}

James jako stały klient, został zaproszony do chińskiej fabryki (konkretnej fabryki). Tam miał możliwość zobaczyć jak produkowane są poszczególne części do hulajnogi.

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

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

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

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

James nie miał problemów z kupowaniem części u chińskiego producenta.

public class Klient
{
	private ChinskaKierownica _chinskaKierownica;
	private ChinskaNaklejka _chinskaNaklejka;
	private ChinskieHamulce _chinskieHamulce;
	private ChinskieKolka _chinskieKolka;

	public void ZamowChinskieElementy()
	{
		var chinskaFabryka = new ChinskaFabryka();
		_chinskieHamulce = chinskaFabryka.WyprodukujHamulce();
		_chinskaNaklejka = chinskaFabryka.WyprodukujNaklejke();
		_chinskieKolka = chinskaFabryka.WyprodukujKolka();
		_chinskaKierownica = chinskaFabryka.WyprodukujKierownice();
	}
}

Chiński producent był tani, bo jego komponenty nie były najwyższej jakości. James na początku z powodzeniem stosował chińskie elementy w budowaniu hulajnóg na sprzedaż. Po pewnym czasie James postanowił wprowadzić droższa wersje hulajnogi. Chińska fabryka nie mogła mu zapewnić części, jakie chciał mieć James do budowy lepszej hulajnogi. James znalazł Polską fabrykę (konkretną fabrykę), która tworzyła elementy do droższej wersji hulajnogi. Pojechał więc do Wrocławia, gdzie mieściła się fabryka, aby zobaczyć jak produkowane są komponenty do hulajnogi.

public class PolskieKolka
{
	public Color Kolor { get; set; }
}

public class PolskaKierownica 
{
	public int Wysokosc { get; set; }
}

public class PolskieHamulce
{
	public int ZywotnoscWMiesiacach { get; set; }
}

public class PolskaNaklejka
{
	public bool Hologram { get; set; }
}


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

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

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

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

James zaczął z powodzenie zamawiać także polskie komponenty do hulanóg.

public class Klient
{
	private ChinskaKierownica _chinskaKierownica;
	private ChinskaNaklejka _chinskaNaklejka;
	private ChinskieHamulce _chinskieHamulce;
	private ChinskieKolka _chinskieKolka;

	public void ZamowChinskieElementy()
	{
		var chinskaFabryka = new ChinskaFabryka();
		_chinskieHamulce = chinskaFabryka.WyprodukujHamulce();
		_chinskaNaklejka = chinskaFabryka.WyprodukujNaklejke();
		_chinskieKolka = chinskaFabryka.WyprodukujKolka();
		_chinskaKierownica = chinskaFabryka.WyprodukujKierownice();
	}

	private PolskaKierownica _polskaKierownica;
	private PolskaNaklejka _polskaNaklejka;
	private PolskieHamulce _polskieHamulce;
	private PolskieKolka _polskieKolka;

	public void ZamowPolskieElementy()
	{
		var polskaFabryka = new PolskaFabryka();
		_polskieHamulce = polskaFabryka.WyprodukujHamulce();
		_polskaNaklejka = polskaFabryka.WyprodukujNaklejke();
		_polskieKolka = polskaFabryka.WyprodukujKolka();
		_polskaKierownica = polskaFabryka.WyprodukujKierownice();
	}
}

W tym momencie James składał dwie wersje hulajnóg: tańsze i droższe. Części do hulajnóg ściągał z fabryki chińskiej oraz fabryki wrocławskiej.

Refactoring

Spójrzmy na kod. Czy widzisz, co można byłoby tutaj udoskonalić?
Klasa klient ma osobna pola na produkty z Chin i produkty z Polski. Kółka to kółka. Nie zależnie czy są z Chin z Polski. Jak można rozwiązać ten problem?
Weźmy pod lupę klasy PolskieKolka oraz ChinskieKolka. Są takie same. Usuńmy tą niepotrzebną redundancje. Stwórzmy zamiast klas PolskieKolka oraz ChinskieKolka klasę Kolka.

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

Poprawny kod PolskiejFabryki i ChinskiejFabryki.

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

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

I zobaczmy jak James może teraz zamawiać nowe produkty:

public class Klient
{
	private Kolka _kolka;

	public void ZamowChinskieKolka()
	{
		var  chinskaFabryka = new ChinskaFabryka();
		_kolka = chinskaFabryka.WyprodukujKolka(); //_kolka.GetType() zwróci Kolka
	}
 
	public void ZamowPolskieKolka()
	{
		var polskaFabryka = new PolskaFabryka();
		_kolka = polskaFabryka.WyprodukujKolka(); //_kolka.GetType() zwróci Kolka
	}
}

Ale co się w tym momencie będzie dziać? James będzie zawiedziony. To czy kółka pochodzą z polskiej fabryki czy z chińskiej fabryki będzie można rozpoznać tylko po kolorze kółek. A co jak Polacy będą produkować czarne kółka? Nie rozpoznamy w późniejszym etapie, kto wyprodukował czarne kółka. Wiec rozwiążmy ten problem inaczej.

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

public class PolskieKolka : Kolka
{ }

public class ChinskieKolka : Kolka
{ }

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

Co dało nam to, że dodaliśmy klasę abstrakcyjną, dziedziczenie? Zobaczmy jak James teraz może zamawiać kółka.

public class Klient
{
	private Kolka _kolka;

	public void ZamowChinskieKolka()
	{
		var  chinskaFabryka = new ChinskaFabryka();
		_kolka = chinskaFabryka.WyprodukujKolka(); //_kolka.GetType() zwróci ChinskieKolka
	}
 
	public void ZamowPolskieKolka()
	{
		var polskaFabryka = new PolskaFabryka();
		_kolka = polskaFabryka.WyprodukujKolka(); //_kolka.GetType() zwróci PolskieKolka
	}
}

Teraz James może zamówić dowolny typ kółek, a to co wyprodukuje zapisać do zmiennej _kolka. Dziedziczenie idealnie nadaje się dla tego przypadku.
Skoro takie rozwiązanie dobrze się sprawdza to zastosujmy dziedzicznie dla pozostałych elementów.

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
{ }

W fabrykach trzeba zmienić klasy jakie zwracają poszczególne metody.

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;
	}
}
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;
	}
}

W klasie Klient usuwamy zbędne pola i piszemy uniwersalne pola.

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

	public void ZamowChinskieElementy()
	{
		var chinskaFabryka = new ChinskaFabryka();
		_hamulce = chinskaFabryka.WyprodukujHamulce();
		_naklejka = chinskaFabryka.WyprodukujNaklejke();
		_kolka = chinskaFabryka.WyprodukujKolka(); //_kolka.GetType() zwróci ChinskieKolka
		_kierownica = chinskaFabryka.WyprodukujKierownice();
	}

	public void ZamowPolskieElementy()
	{
		var polskaFabryka = new PolskaFabryka();
		_hamulce = polskaFabryka.WyprodukujHamulce();
		_naklejka = polskaFabryka.WyprodukujNaklejke();
		_kolka = polskaFabryka.WyprodukujKolka(); //_kolka.GetType() zwróci PolskieKolka
		_kierownica = polskaFabryka.WyprodukujKierownice();
	}
}

Jak widać to, że zostały wprowadzone klasy abstrakcyjne dla poszczególnych produktów daje nam możliwość pozbycia się dużej ilości pół w klasie Klient.

Co tutaj jeszcze można poprawić? Metody ZamowPolskieElementy oraz ZamowChinskieElementy nie wyglądają tak źle. No ale co jakby James zamawiał z 50 różnych fabryk elementy? Pisać 50 różnych metod? Troszkę za dużo. Polska i Chińska fabryka nie różnią się nazwami metod. Wprowadźmy ich dodatkowy element abstrakcji. Może interfejs? Można. Ale skoro nazwa tego wzorca projektowego to fabryka abstrakcyjna to wprowadźmy klasę abstrakcyjną.

public abstract class FabrykaAbstrakcyjna
{
	public abstract Kolka WyprodukujKolka();
	public abstract Kierownica WyprodukujKierownice();
	public abstract Naklejka WyprodukujNaklejke();
	public abstract Hamulce WyprodukujHamulce();
}

Poprawmy kod obydwu fabryk:

public class ChinskaFabryka : FabrykaAbstrakcyjna
{
	public override Kolka WyprodukujKolka()
	{
		...
	}

	public override Kierownica WyprodukujKierownice()
	{
		...
	}

	public override Hamulce WyprodukujHamulce()
	{
		...
	}

	public override Naklejka WyprodukujNaklejke()
	{
		...
	}
}

public class PolskaFabryka : FabrykaAbstrakcyjna
{
	public override Kolka WyprodukujKolka()
	{
		...
	}

	public override Kierownica WyprodukujKierownice()
	{
		...
	}

	public override Hamulce WyprodukujHamulce()
	{
		...
	}

	public override Naklejka WyprodukujNaklejke()
	{
		...
	}
}

I zobaczmy jak teraz James może zamawiać nowe produkty.

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

	public Klient(FabrykaAbstrakcyjna fabrykaAbstrakcyjna)
	{
		_kierownica = fabrykaAbstrakcyjna.WyprodukujKierownice();
		_naklejka = fabrykaAbstrakcyjna.WyprodukujNaklejke();
		_hamulce = fabrykaAbstrakcyjna.WyprodukujHamulce();
		_kolka = fabrykaAbstrakcyjna.WyprodukujKolka();
	}
}

James jako klient ma teraz do dyspozycji wszystkie niezbędne elementy do produkcji hulajnogi. Co może z nimi zrobić? Na przykład może sprawdzić wysokość kierownicy.

public class Klient
{
	public void SprawdzWysokoscKierownicy()
	{
		Console.WriteLine(_kierownica.Wysokosc);
	}
}

Sprawdźmy jak to będzie działać w praktyce.

class Program
{
	static void Main(string[] args)
	{
		FabrykaAbstrakcyjna fabryka = new PolskaFabryka();
		Klient James = new Klient(fabryka);
		James.SprawdzWysokoscKierownicy();
	}
}

Trochę teorii:

Poniższy diagram UML przedstawia wzorzec projektowy Fabryka Abstrakcyjna.

abstractFactory
Źródło: www.dofactory.com

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

Klient jako Client
FabrykaAbstrakcyjna jako AbstractFactory
ChinskaFabryka jako ConcretFactory1
PolskaFabryka jako ConcretFactory2
Kolka jako AbstractProductA
ChinskieKolka jako ProductA1
PolskieKolka jako ProductA2
Kierownica jako AbstractProductB
ChinskaKierownica jako ProductB1
PolskaKierownica jako ProductB2

Wzorzec ten został opisany przez Bandę Czworga (Gang of Four). Wzorzec projektowy Fabryka Abstrakcyjna należy do wzorców kreacyjnych. Problem, który pomaga rozwiązać Fabryka Abstrakcyjna dotyczy implantacji klas. W momencie kiedy w programie klasy posiadają wiele różnych implementacji, fabryki mają za zadanie zgrupować różne implementacje obiektów i je stworzyć. Fabryki tworzą instancje klas, które pochodzą z jednej rodziny – czyli w naszym przykładzie jedna fabryka odpowiada za stworzenie obiektów z polskich rodziny, a druga z chińskiej rodziny.

Codeproject – przykład 1 (ang)
Codeproject – przykład 2 (ang)
DoFactory – przykład (ang)
Wikipedia (ang)
Dotnetpage – przykład (pl)
Siszarp – przykład (pl)

Leave a Comment

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *