Wzorce projektowe – wzorzec budowniczy.
W dzisiejszych czasach jest wiele reguł, wzorców i zasad programowania, którymi kierują się obecni programiści. Żeby ich kod był zrozumiały i jasny dla innych oraz wydajny posługują się oprócz zasad SOLID, tzw. wzorcami projektowymi. Wzorce projektowe są to uniwersalne, sprawdzone w praktyce rozwiązanie często pojawiających się, powtarzalnych problemów projektowych. Wzorzec pokazuje powiązania i zależności pomiędzy klasami oraz obiektami i ułatwia tworzenie, modyfikację oraz utrzymanie kodu źródłowego. Jest opisem rozwiązania, a nie jego implementacją. Wzorce projektowe stosuje się w projektach wykorzystujących programowanie obiektowe. Jest wiele wzorców projektowych jednym z nich jest wzorzec budowniczy. W tym artykule opisze ten wzorzec oraz przedstawię przykład kodu programu przed zastosowaniem tego wzorca jak i po.
Wstęp
Wzorzec budowniczy z ang. Builder, używamy do hermetyzowania tworzenia produktu i w celu umożliwienia jego wieloetapowego inicjowania oraz rozdzieleniu sposobu tworzenia obiektów od ich reprezentacji. Innymi słowy proces tworzenia obiektu podzielony jest na kilka mniejszych etapów a każdy z tych etapów implementuje się na wiele sposobów. Dzięki takiemu rozwiązaniu możliwe jest tworzenie różnych reprezentacji obiektów w tym samym procesie konstrukcyjnym: sposób tworzenia obiektów zamknięty jest w oddzielnych obiektach zwanych konkretnymi budowniczymi. Wzorzec budowniczy różni się od innych wzorców kreacyjnych tym, że skupia się na sposobie tworzenia obiektów reprezentujących produkty. Wzorzec ten spełnia zasadę odwrócenia zależności oraz zasadę otwarte/zamknięte, otwarte na rozbudowę, zamknięte na modyfikację (łatwo do kodu można dodać nowych budowniczych). Należy do grupy wzorców skatalogowanych przez Gang czworga.
Zastosowanie
Wzorzec budowniczy stosuje się do oddzielenia sposobu tworzenia obiektów od tego jak te obiekty mają wyglądać. Przykładem jest oprogramowanie konwertujące tekst z jednego formatu na drugi. Algorytm odczytujący i interpretujący dane wejściowe oddziel się od algorytmu tworzącego dane wyjściowe. Dzięki takiemu rozwiązaniu możliwe jest zastosowanie jednego obiektu odczytującego dane wejściowe oraz wielu obiektów konwertujących odczytane dane do różnych formatów (ASCII, HTML, RTF, itp.), co zwiększa uniwersalność rozwiązania.
Innym przykładem gdzie można zastosować ten wzorzec jest aplikacja do sprzedaży turnusów wycieczkowych. Klienci mogą wybrać hotel i różne typy biletów, rezerwować miejsca w restauracjach, a także zamówić bilety na dodatkowe imprezy. Program turnusu dla klientów może się różnic w zależności od wyboru opcji w pakiecie. Potrzebna więc nam jest elastyczna struktura danych, która może reprezentować plan pojedynczego klienta w każdej potencjalnej odmianie.
Przykład
Poniżej znajduje się niepoprawny kod bez zastosowania wzorca budowniczy. Występuje tam inicjalizacja obiektu klasy Car. Program jest przeplatany logiką, w pewnych miejscach zostały użyte instrukcje warunkowe a gdzie indziej wzorzec prostej fabryki.
var smallCar = new Car();
if (budget > 4500)
{
smallCar.Wheels = "Aluminium rims 17 inches";
}
else
{
smallCar.Wheels = "Steel rims 15 inches";
}
smallCar.Addons = new List<string>();
smallCar.Addons.Add("CD radio with MP3");
smallCar.Addons.Add("CD radio");
smallCar.Engine = EngineFactory.CreateEngine("120 HP engine");
Kod 1. Kod programu bez użycia wzorca
Program jest stosunkowo prosty, jednak gdyby trzeba było zbudować większy samochód, musielibyśmy skopiować cały ten kod i zmienić poszczególne parametry. Byłoby to podejście bardzo imperatywne, raczej mało obiektowe. W dużym projekcie wielu programistów konstruowałoby instancje klasy Car na swój sposób. Chcąc zmienić proces ich budowy, należałoby to zrobić w wielu miejscach.
Poniżej przestawiony jest kod programu z użyciem wzorca budowniczy:
// kierownik - logika inicjalizacji obiektu
class CarDirector
{
public void ConstructCar(ICarBuilder builder)
{
builder.BuildWheels();
builder.BuildAddons();
builder.BuildEngine();
}
}
// budowniczy - interfejs implementacji
interface ICarBuilder
{
void BuildWheels();
void BuildAddons();
void BuildEngine();
Car GetResult();
}
// konkretny budowniczy - implementacja
class SmallCarBuilder : ICarBuilder
{
private Car car = new Car();
private EngineFactory engineFactory = new EngineFactory();
public void BuildWheels()
{
car.Wheels = "Steel rims 15 inches";
}
public void BuildAddons()
{
car.Addons = new List();
car.Addons.Add("CD radio with MP3");
car.Addons.Add("CD radio");
}
public void BuildEngine()
{
car.Engine = engineFactory.CreateEngine("120 HP engine");
}
public Car GetResult()
{
return car;
}
}
static void Main(string[] args)
{
CarDirector carDirector = new CarDirector();
ICarBuilder smallCarBuilder = new SmallCarBuilder();
// buduj według logiki CarDirector używając implementacji z SmallCarBuilder
carDirector.ConstructCar(smallCarBuilder);
Car smallCar = smallCarBuilder.GetResult();
}
Kod 2. Kod programu z użyciem wzorca
Dzięki użyciu wzorca główna metoda programu znacząco się uprościła. Uzyskaliśmy jednolity interfejs polimorficzny służący do tworzenia instancji samochodu.
Podsumowanie
Podsumowując, wzorzec ten stosuje się głównie do budowania struktur kompozytowych. Posiada wiele zalet takich jak: hermetyzuje operacje niezbędne do stworzenia złożonego obiektu oraz umożliwia tworzenie obiektów w procedurze wielokrokowej i nie narzuca tej procedury. Ukrywa wewnętrzną reprezentację produktu przed klientem. Implementacje produktów można wymieniać, ponieważ klient korzysta wyłącznie z abstrakcyjnego interfejsu. Wadą jaką posiada ten wzorzec jest to, że tworzenie obiektów wymaga od klientów większej wiedzy z zakresu dziedziny problemu niż w przypadku stosowania wzorca Fabryka.