Wzorzec Budowniczy

BUDOWNICZY

Jest to jeden z wzorców projektowych zaliczany do grupy wzorców konstrukcyjnych. Proces tworzenia obiektu jest tutaj oddzielony od jego reprezentacji rzeczywistej. 

Istnieją jednak dwie popularne odmiany tego wzorca co może prowadzić do niejasności w jego zrozumieniu. Pierwsza odmiana została opisana przez autorów ksiażki Design Patterns (Erich Gamma, Richard Helm, Ralph Johnson, John Vissides – zwanymi również jako GoF – Gang of Four). Druga odmiana (będąca modyfikacją pierwszej) została przedstawiona przez Joshua Bloch. Dlatego w tym artykule chciałbym po krótce omówić obie te metody.

Odmiana GoF

Budowniczy używany jest najcześciej w sytuacjach kiedy: 

  • Proces tworzenia obiektu jest wieloetapowy i nie można utworzyć instancji tego obiektu przez jednorazową operację.
  • Nie jesteśmy w stanie uprościć złożoności tworzonego obiektu.
  • Obiekt będzie budowany wielokrotnie i na wiele sposobów.

Podstawą wzorca jest istnienie interfejsu Budowniczy przy pomocy którego będziemy budować rzeczywiste obiekty. Spróbujmy na przykładzie poniżej klasy Samochód napisać proces montażu jego egzemplarza.

public class Samochod {

private String kola;
private String silnik;
private String konstrukcja;

public void ustawKola(String kola){
this.kola = kola;
}
public void ustawSilnik(String silnik){
this.silnik = silnik;
}
public void ustawKonstrukcja(String konstrukcja){
this.konstrukcja = konstrukcja;
}

public String podajKola() {return kola;}
public String podajSilnik() {return silnik;}
public String podajKonstrukcja() {return konstrukcja;}
}

Stwórzmy zatem wspomnianego wcześniej Budowniczego jako interfejs, który będzie zawierał niezbędne metody do montażu samochodu:

public interface Budowniczy{

void montujeKola();
void montujeSilnik();
void montujeKonstrukcje();

Samochod montazSamochodu();
}

W następnej kolejności utworzymy rzeczywistą klasę BudowniczyMałychSamochodów z wykorzystaniem metod zawartych w Budowniczym:

public class BudowniczyMalychSamochodow implements Budowniczy{

private Samochod samochod = new Samochod();

@Override
public void montujeKola() {
samochod.ustawKola("Male kola");
}

@Override
public void montujeSilnik() {
samochod.ustawSilnik("Maly silnik");
}

@Override
public void montujeKonstrukcje() {
samochod.ustawKonstrukcja("Konstrukcja do samochodu malego");
}


public Samochod montazSamochodu(){

System.out.println(
"\n" + samochod.podajKola() +
"\n" + samochod.podajSilnik() +
"\n" + samochod.podajKonstrukcja()
);

System.out.println("\nNowe male autko jest juz gotowe");
return samochod;
}
}

Pozostało nam już tylko zatrudnić rzeczywistego kierownika który będzie odpowiedzialny za dostarczenie danego modelu samochodu. Kierownik ma do dyspozycji zaufanego montera który wie doskonale jak zmontować dany model samochodu:

public class KierownikMontazu {

public void ZbudujSamochod(Budowniczy monter){

monter.montujeKola();
monter.montujeSilnik();
monter.montujeKonstrukcje();
}
}

Teraz nasz BudowniczyMałychSamochodów gotowy jest do tworzenia nowych obiektów klasy Samochód. Przetestujmy nasze rozwiązanie poprzez zbudowanie nowego samochodu.

public class TestNowegoSamochodu {
public static void main (String[] args){

KierownikMontazu kierownikMontazu = new KierownikMontazu();
BudowniczyMalychSamochodow malySamochod = new BudowniczyMalychSamochodow();

kierownikMontazu.ZbudujSamochod(malySamochod);

// tutaj pokazmy tylko dla naszych celow jak wyglada proces montazu
malySamochod.montazSamochodu();
}
}

Oto wynik:

Male Kola
Maly silnik
Konstrukcja do samochodu malego
Nowe male autko jest juz gotowe

Podsumowanie GoF

Na powyższym przykładzie widzimy jak zbudowana jest struktura tego wzorca. Mamy tutaj wspomniany interfejs Budowniczy przy użyciu którego tworzymy rzeczywistą konkretną klasę BudowniczyMałychSamochodów. Widzimy że KierownikMontażu przy użyciu realnego budowniczego (montera) jest w stanie dostarczyć nam konkretny model samochodu. Wzorzec ten jest najczęsciej stosowany do budowy złożonych struktur kompozytowych.

Odmiana Statyczny Budowniczy

Wykorzystajmy znaną już nam klasę Samochód w naszym przykładzie a następnie zmodyfikujmy ją odrobinę dodając wewnątrz tej klasy statyczną klasę Monter która będzie naszym Statycznym Budowniczym.

Dodatkowo ustawmy konstruktor klasy Monter z jednym obowiązkowym parametrem (kola) oraz dwoma opcjonalnymi parametrami (silnik oraz konstrukcja). 

public class Samochod {

private final int kola;
private final String silnik;
private final String konstrukcja;

private Samochod(Monter monter) {
this.kola = monter.kola;
this.silnik = monter.silnik;
this.konstrukcja = monter.konstrukcja;
}

public String toString() {
return "Samochod zbudowany: \n Kola rozmiaru: " + this.kola + ", " + this.silnik + ", " + this.konstrukcja;
}

public static class Monter{
private final int kola;
private String silnik;
private String konstrukcja;


public Monter(int kola){
this.kola = kola;
}

public Monter silnik(String silnik){
this.silnik = silnik;
return this;
}

public Monter konstrukcja(String konstrukcja){
this.konstrukcja = konstrukcja;
return this;
}

public Samochod zbuduj() {

if (!List.of(15,17,19).contains(this.kola)) {
throw new IllegalArgumentException("Rozmiar kola musi byc 15, 17, 19");
}

return new Samochod(this);
}
}
}

W metodzie zbuduj() klasy Samochód sprawdzamy czy rozmiar ustawionych dla danego modelu kół zgadza sie z naszą specyfikacja. 

Tak przygotowaną klasę Samochód wykorzystamy do utworzenia nowych obiektów tej klasy:

public class TestNowegoSamochodu {
public static void main(String[] args){
Samochod malySamochod = new Samochod.Monter(17)
.silnik("Maly silnik 1.0")
.konstrukcja("Konstrukcja do malego samochodu")
.zbuduj();
System.out.println(malySamochod);

Samochod duzySamochod = new Samochod.Monter(19)
.silnik("Szesciocylindrowy silnik rzedowy")
.konstrukcja("Konstrukcja rozmiaru jeep")
.zbuduj();

System.out.println(duzySamochod);
}
}

Oto wynik:

Samochod zbudowany:
Kola rozmiaru: 17, Maly silnik 1.0, Konstrukcja do malego samochodu
Samochod zbudowany:
Kola rozmiaru: 19, Szesciocylindrowy silnik rzedowy, Konstrukcja rozmiaru jeep

Widzimy jak w sposób dynamiczny utworzyliśmy dwa różne  modele samochodów. Jednocześnie sprawdziliśmy czy podany rozmiar kól zgadza się z przyjętą przez nas specyfikacją.

Oczywiście odbyło się to kosztem znacznego rozbudowania klasy Samochód jednak pozatym nie mamy już innch klas które wchodzą w skład finalnego produktu. Statyczny budowniczy jest często stosowany do budowania obiektów o wieloparametrowych konstruktorach.

Podsumowanie

Mam nadzieję że przedstawienie powyższych przykładów pomoże Wam spojrzeć na wzorzec budowniczy z kilku perspektyw gdyż sam czytając inne artykuły nie rozumiałem dlaczego w jednych przypadkach stosowana jest metoda statyczna natomiast w innych kod opiera sie na fundamencie klasy abstrakcyjnej i kierownika.

Leave a Comment

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