Skip to content

Budowniczy (Builder)

Przeznaczenie

Głównym założeniem wzorca konstrukcyjnego jakim jest Budowniczy (Builder) jest oddzielenie tworzenia obiektów od ich reprezentacji. Dzięki takiemu rozwiązaniu można w wyniku tego samego procesu konstrukcji otrzymać różne reprezentacje. Innymi słowy dzięki temu wzorcowi w wyniku działania jednego algorytmu odpowiedzialnego za budowę jakiegoś obiektu, możemy uzyskać różne obiekty. Zastosowanie tego wzorca sprawia, że algorytmy odpowiedzialne za utworzenie obiektu są ukryte za pewną wcześniej utworzoną abstrakcją (co powinno być rzeczą ogólnie pożądaną w procesie tworzenie oprogramowania).

Uzasadnienie

Standardowym przykładem przedstawianym podczas prezentacji budowniczego jest m.in. oprogramowanie konwertujące pliki tekstowe. Załóżmy, że mamy program jakim jest czytnik plików o rozszerzeniu .docx. Rzeczą naturalną dla takiego programu byłaby prawdopodobnie możliwość zapisu tych plików tekstowych do innych formatów takich jak np.: .txt, .rtf, .doc. Możemy więc łatwo sobie wyobrazić, że program ten musiałby być wyposażony w moduły odpowiedzialne za konwersję zawartości otwartego pliku do jednego z formatów docelowych. Należałoby również pamiętać o możliwości łatwego dokładania nowych, dodatkowych formatów docelowych. W skrócie musielibyśmy zadbać o łatwą możliwość rozbudowy bez konieczności modyfikacji oryginału. Diagram UML mógłby wyglądać więc następująco (zakładam, że TXT Text to po prostu zwykły tekst ASCII):

Diagram UML

Każda podklasa TextConverter jest wyspecjalizowana względem pewnego konkretnego formatu pliku. Sama klasa DOCXReader również odpowiada za decyzję, z której podklasy należy skorzystać. Jak widać moduł konwertujący jest całkowicie oddzielony od samego czytnika. W razie potrzeby wystarczyłoby tylko odpowiednio dostroić naszą klasę DOCXReader, abyśmy mogli konwertować dowolny rodzaj pliku.

Kiedy stosować

Budowniczego powinniśmy wykorzystywać gdy:

  • Algorytm tworzenia złożonego obiektu ma być niezależny od jego własnych składników oraz sposobu ich łączenia
  • Proces konstrukcji ma umożliwiać tworzenie różnych wersji generowanego obiektu

Struktura

Jeżeli z powyższego diagramu UML chcielibyśmy wyciągnąć ogólny model, to można by przedstawić go w następujący sposób:

Diagram UML wzorca Budowniczy

Elementy składowe

  • Builder (TextConverter) – czyli nasz budowniczy. Definiuje interfejs abstrakcyjny do tworzenia składników będących produktami.
  • ConcreteBuilder (TXTConverter, RTFConverter, DOCConverter) – czyli nasi budowniczowie konkretni. Są oni odpowiedzialni za:
    • Tworzenie oraz łączenie składników produktów w implementacji interfejsu klasy Builder
    • Definiowanie i śledzenie generowanych reprezentacji
    • Udostępniania interfejsu do pobrania produktów (np.: getRtf(), getDoc())
  • Director (DOCXReader) – nadzorca/kierownik. Jest odpowiedzialny za tworzenie obiektów za pomocą interfejsu klasy typu Builder.
  • Product (TXT Text, RTF Text, DOC Text) – są to produkty. Są reprezentacją wygenerowanego obiektu złożonego.

Współdziałanie

Współdziałanie klas typu Builder oraz Director z klientem można przedstawić następującym diagramem interakcji:

Diagram interakcji wzorca Budowniczy

Konsekwencje

  • Możliwość modyfikowania wewnętrznej reprezentacji (wersji) obiektu – wystarczy wybrać odpowiednia klasę typu Concrete Builder
  • Odizolowanie reprezentacji od kodu służącego do tworzenia produktu – klient nie posiada informacji w jaki sposób są tworzone poszczególne wersje obiektów. Ważnym jest aby interfejs klasy typu Builder był na tyle ogólny, aby mógł obsłużyć bogatą gamę generowanych reprezentacji obiektów.
  • Większa kontrola nad procesem tworzenia – wzorzec Budowniczy jest wzorcem, który stawia na generowanie obiektów krok po kroku pod kontrolą obiekty klasy typu Director. Klasa Director otrzymuje gotowy produkt od obiektu klasy typu Builder.

Przykład

Stwórzmy prostą aplikację będącą nieco zmodyfikowaną wersją tej z powyżej opisywanego przykładu. Będzie ona imitowała konwersję pliku z .docx do innych formatów. Jej kod mógłby wyglądać następująco:

/**
 * Created by Lukasz Pusz on 31.03.17.
 * www.lukaszpusz.pl
 */
public abstract class TextConverterBuilder {
    protected TextFile textFile;

    public abstract void convertText();
    public void convertFont() {}
    public void convertParagraph() {}

    public TextFile getTextFile() {
        return textFile;
    }

    public void constructTextFile() {
        System.out.println("Constructing text file...");
        textFile = new TextFile();
    }
}

public class TXTConverterBuilder extends TextConverterBuilder {
    @Override
    public void convertText() {
        System.out.println("Converting text from .docx file to TXT file...");
        textFile.setFileName("txt_file");
        textFile.setExtension(".txt");
    }
}

public class RTFConverterBuilder extends TextConverterBuilder {
    @Override
    public void convertText() {
        System.out.println("Converting text from .docx file to TXT file...");
        textFile.setFileName("rtf_file");
        textFile.setExtension(".rtf");
    }

    @Override
    public void convertFont() {
        System.out.println("Converting fonts...");
    }

    @Override
    public void convertParagraph() {
        System.out.println("Converting paragraphs...");
    }
}

public class DOCConverterBuilder extends TextConverterBuilder {
    @Override
    public void convertText() {
        System.out.println("Converting text from .docx file to TXT file...");
        textFile.setFileName("doc_file");
        textFile.setExtension(".doc");
    }

    @Override
    public void convertFont() {
        System.out.println("Converting fonts...");
    }

    @Override
    public void convertParagraph() {
        System.out.println("Converting paragraphs...");
    }
}

class DOCXReader {
    private TextConverterBuilder converterBuilder;

    void constructTextFile() {
        converterBuilder.constructTextFile();
        converterBuilder.convertText();
        converterBuilder.convertFont();
        converterBuilder.convertParagraph();
    }

    TextFile getTextFile() {
        return converterBuilder.getTextFile();
    }

    void setConverterBuilder(TextConverterBuilder converterBuilder) {
        this.converterBuilder = converterBuilder;
    }
}

public class Main {
    public static void main(String[] args) {
        DOCXReader docxReader = new DOCXReader();

        TXTConverterBuilder txtConverterBuilder = new TXTConverterBuilder();
        RTFConverterBuilder rtfConverterBuilder = new RTFConverterBuilder();

        docxReader.setConverterBuilder(rtfConverterBuilder);
        docxReader.constructTextFile();

        TextFile txtFile = docxReader.getTextFile();
        System.out.println(txtFile);
    }
}

Aplikacja zwróci następujące wyjście:

Constructing text file...
Converting text from .docx file to TXT file...
Converting fonts...
Converting paragraphs...
TextFile: name = rtf_file, extension = .rtf

Oczywiście przykład jest stworzony tylko i wyłącznie w celach poglądowych i został znacznie spłycony. Chodzi mi głównie tutaj o klasę TextFile, która w normalnej sytuacji powinna być co najwyżej klasą abstrakcyjną, po której dziedziczyłyby klasy reprezentujące konkretne typy plików.


Źródła:

Kod znajduje się również na GitHubie pod adresem:

Facebooktwitterredditlinkedinmail
Published inProgramowanieWzorce projektowe