Wzorzec projektowy Twoim wzorcem cz. 3. Budowniczy / Design Pattern is your pattern pt. 3. Builder

PL

Omówienie

Dzisiaj czas na kolejny wpis poświęcony wzorcom kreacyjnym, a konkretnie na omówienie wzorca projektowego Builder. Builder jest wzorcem, który dzieli implementację obiektu na etapy.

Uwaga We wpisie będę naprzemiennie używał słów domena i produkt mając na myśli dokładnie tą samą rzecz.

Przykłady z życia

Wyobraźmy sobie sytuację, w której w naszej aplikacji istnieje dosyć mocno rozbudowany obiekt domeny, którego charakter pozwala na budowę jego poszczególnych części oraz że kolejne etapy budowy tego obiektu tworzą jego poszczególne reprezentacje.

Wyobraźmy sobie dalej sytuację w której obiekt domeny zawiera w sobie property albo pole, którego wartość jest zależna od innego obiektu i może zostać zainicjalizowana dopiero w późniejszym etapie, już po powołaniu instancji naszego obiektu docelowego.

Załóżmy, że mamy obiekt domeny Book, która zawiera w sobie property ReadersNumber – mówi ono o tym, ile razy książka była przeczytana, natomiast o tej liczbie powie nam wartość zapytania zastosowana w tabeli Readers z wykorzystaniem Id danej książki – tabele Book i Reader są w relacji 0,1 do wielu i łączą one czytelników z książkami. Widzimy, że o liczbie czytelników dowiemy się, dopiero po odpytaniu bazy danych, ale przedtem chcielibyśmy jednak powołać obiekt klasy Book.

Ponadto, często oczekujemy na wartość mającą być zwróconą z zewnętrznego serwisu np. przez zewnętrzne REST API.

Mamy więc do czynienia z sytuacjami, gdzie istnieje możliwość tworzenia różnych reprezentacji obiektu domeny. Druga z nich to sytuacja polegająca na stopniowej budowie obiektu spowodowanej nie tylko przez warunki zewnętrzne.

Nie ukrywam, że to pierwsza z opisanych sytuacji reprezentuje charakter wzorca Builder, natomiast wspomniałem też o tej drugiej z racji powiązania pośredniego z prezentowaną przez Builder ideą projektowania.

Odpowiedzi na dwa zasadnicze pytania, które zadaję zawsze w przypadku zastosowania danego wzorca projektowego znajdują się poniżej.

Czemu Builder?

  • Przez podział implementacji obiektu na etapy ten sam proces tworzenia może mieć inne reprezentacje;
  • Produkt nie jest tworzony za jednym razem, ale tworzony krok po kroku;
  • Dostarcza interfejs do tworzenia wszystkich możliwych reprezentacji obiektu;
  • Klient aplikacji w celu powołania interesujących go builderów powołuje klasę Reader (w przykładowej implementacji poniżej będzie to klasa BuiderAbstract) a nie bezpośrednio instancje klas domeny;
  • Klient w celu powołania odpowiednich reprezentacji klasy domeny powołuje właśnie konkretnego Buildera;

Jak zaimplementować?

  • Dostarcza klasę abstrakcyjną dla poszczególnych builderów implementacji;
  • W klasie abstrakcyjnej dla builderów znajdują się: property wskaźnik na instancję klasy domeny, metody powołujące i zwracające instancję klasy produktowej oraz poszczególne metody budujące implementację klasy domeny;
  • Dostarcza poszczególne buildery zawierające metody budujące implementację klasy domeny w sprecyzowany w builderze sposób – metody te wywołują metody kreacyjne (statyczne) znajdujące się w klasie domeny;
  • Ponadto dostarcza buildera kompozycyjnego, dzięki któremu możemy wywołać wszystkie poszczególne etapy implementacji w określonej kolejności i zbudować finalną implementację obiektu domeny – żeby było to możliwe, i co więcej zgodne z budowanym schematem – builder kompozycyjny zawiera metodę powołującą odpowiedniego buildera i przypisującą do niego właściwy produkt (obiekt domeny).

Przykładowa implementacja w C#

Klasa domeny:


    /// <summary>
    /// Main product class which instances will be created during the program.
    /// </summary>
    public class Product
    {
        /// <summary>
        /// Set of properties that will be partially set during creating Product's class instance.
        /// </summary>
        public string Property1 { get; private set; }
        public string Property2 { get; private set; }
        public string Property3 { get; private set; }

        /// <summary>
        /// The class constructor.
        /// </summary>
        /// <param name="property1">The property1.</param>
        /// <returns>Instance of Product.</returns>
        public static Product Create()
        {
            return new Product();
        }

        /// <summary>
        /// Sets Property1 value of the Product's instance.
        /// </summary>
        /// <param name="property1">The property1.</param>
        /// <returns>Partially initialized Product's instance.</returns>
        public Product SetProperty1(string property1)
        {
            this.Property1 = property1;

            return this;
        }

        /// <summary>
        /// Sets Property2 value of the Product's instance.
        /// </summary>
        /// <param name="property2">The property2.</param>
        /// <returns>partially initialized Product's instance.</returns>
        public Product SetProperty2 (string property2)
        {
            this.Property2 = property2;

            return this;
        }

        /// <summary>
        /// Sets Property3 value of the Product's instance.
        /// </summary>
        /// <param name="property3">The property3.</param>
        /// <returns>Partially initialized Product's instance.</returns>
        public Product SetProperty3(string property3)
        {
            this.Property3 = property3;

            return this;
        }
    }

Klasa abstrakcyjna dostarczająca logikę dla poszczególnych builderów:


    /// <summary>
    /// Abstract class that is base class for implementation of particular builders BuilderA and BuilderB.
    /// </summary>
    public abstract class BuilderAbstract
    {
        /// <summary>
        /// Product member delivers possibility of performing particular operations on it.
        /// </summary>
        /// <seealso cref="Builder.Product">
        protected Product Product { get; private set; }

        /// <summary>
        /// Creates new instance of Product class using its Create() creational method and assigns this instance to the Product property.
        /// </summary>
        /// <seealso cref="Builder.Product">
        public void CreateProduct()
        {
            Product = Product.Create();
        }

        /// <summary>
        /// Delivers Product property.
        /// </summary>
        /// <returns>The Product property.</returns>
        /// <seealso cref="Builder.Product">
        public Product GetProduct()
        {
            return this.Product;
        }

        /// <summary>
        /// Methods to be overrided.
        /// </summary>
        public abstract void BuildProperty1();
        public abstract void BuildProperty2();
        public abstract void BuildProperty3();
    }

Pierwszy builder dziedziczący po wyżej zaimplementowanej klasie abstrakcyjnej i budujący klasę domeny:


    /// <summary>
    /// First builder that inherits from BuilderAbstract and delivers building method
    /// </summary>
    /// <seealso cref="BuilderAbstract">
    public class BuilderA : BuilderAbstract
    {
        /// <summary>
        /// Sets Property1 of the Product instance.
        /// </summary>
        public override void BuildProperty1()
        {
            Product.SetProperty1("property1_A");
        }

        /// <summary>
        /// Sets Property2 of the Product instance.
        /// </summary>
        public override void BuildProperty2()
        {
            Product.SetProperty2("property2_A");
        }

        /// <summary>
        /// Sets Property2 of the Product instance.
        /// </summary>
        public override void BuildProperty3()
        {
            Product.SetProperty3("property3_A");
        }
    }

Drugi builder dziedziczący po wyżej zaimplementowanej klasie abstrakcyjnej i budujący klasę domeny:


    /// <summary>
    /// Second builder that inherits from BuilderAbstract and delivers building method
    /// </summary>
    /// <seealso cref="BuilderAbstract"> 
    public class BuilderB : BuilderAbstract
    {
        /// <summary>
        /// Sets Property1 of the Product instance.
        /// </summary>
        public override void BuildProperty1()
        {
            Product.SetProperty1("property1_B");
        }

        /// <summary>
        /// Sets Property2 of the Product instance.
        /// </summary>
        public override void BuildProperty2()
        {
            Product.SetProperty2("property2_B");
        }

        /// <summary>
        /// Sets Property2 of the Product instance.
        /// </summary>
        public override void BuildProperty3()
        {
            Product.SetProperty3("property3_B");
        }
    }

Composition builder pozwalający na wywołanie zestawu metod poszczególnego builder i skomponowanie ich w cały obiekt domeny:


    /// <summary>
    /// Invokes whole composition process and allows to create Product's class instance.
    /// </summary>
    public class CompositionBuilder
    {
        /// <summary>
        /// BuilderAbstract property - member that delivers possibility of invoking particular building methods.
        /// </summary>
        public BuilderAbstract Builder { get; private set; }

        /// <summary>
        /// Sets given BuilderAbstract instance to the Builder property.
        /// </summary>
        /// <param name="builder">Instance of the particular builder class.</param>
        public void SetBuilderAbstract (BuilderAbstract builder)
        {
            this.Builder = builder;
        }

        /// <summary>
        /// Delivers Product's instance.
        /// </summary>
        /// <returns>Product's instance.</returns>
        public Product GetProduct()
        {
            return this.Builder.GetProduct();
        }

        /// <summary>
        /// Invokes all building methods deriving from particular builder class and creates Product's instance.
        /// </summary>
        public void BuildComposition()
        {
            this.Builder.BuildProperty1();
            this.Builder.BuildProperty2();
            this.Builder.BuildProperty3();
        }
    }

Przykładowe powołanie instancji klasy produktowej z wykorzystaniem buildera BuilderA:


var compositionBuilder = new CompositionBuilder();
var builderA = new BuilderA();

compositionBuilder.SetBuilderAbstract(builderA);
compositionBuilder.Builder.CreateProduct();

var product = compositionBuilder.GetProduct();
compositionBuilder.BuildComposition();

Console.WriteLine(product);
Console.WriteLine(product.Property1);
Console.WriteLine(product.Property2);
Console.WriteLine(product.Property3);
Console.Read();

Wróć na stronę startową

ENG

Introduction

Today is the time for another post concerning creational design patterns, and to be precisely – Builder design pattern. Builder is the pattern which divides implementation of an object into stages.

Remark In the post I will use words domain and product alternately having on mind exactly the same thing.

Real life examples

Suppose that we have to deal with a situation when in our application exists quite complex domain object whose character allows building its particular parts and that each another phase of this process creates its particular representations.

Suppose further that we deal with situation when domain object contains property or field which value depends on some other object and its initialization is possible in later time – after instance of our object is created.

Assume that we have Book domain object which contains ReadersNumber property  that provides information how many times particular book has been read and its value is being received from query processed within Readers table using particular Book Id– tables Book and Reader are in 0,1 – many relation and they combine books with its readers. We can see that about number of readers we would know after querying a database, however we would like to invoke our Book object firstly.

However, often we wait for receiving value that is going to be returned from external or internal service, for example from internal or external REST API.

Hence we have situations where: exists possibility of creating many different representations of our domain object and – secondly – where exists possibility of creating object in phases, caused not only by external conditions.

To be honest firstly described situation is the true representation of the Builder design but I had to mention about the second one – concerning books and readers – because of its indirect similarity to the Builder idea.

Answers on the questions I always ask when it comes to design patterns usage are placed below.

Why Builder?

  • Since implementations is divided into stages, the same creation process can have many different representations
  • Product is not created at once, but in steps
  • Delivers interface according to the creation of all possible representations of domain object
  • In order to the creation of builders, Client invokes Reader (in the example of implementation below it will be BuiderAbstract class) instead of indirect invoking instances of domain class
  • In order to invoking concrete representations of a domain class object, Client invokes right particular Builder

How to implement?

  • Delivers an abstract class for particular implementation builders
  • Mentioned abstract class contains: reference pointer member to product class, methods responsible for invoking and delivering instances of product class and particular methods responsible for building instances of product class
  • Delivers particular builders containing methods responsible for building implementations of particular product class in specific way for each builder – these methods invoke creational methods (statyczne) from product class
  • However it delivers composition builder – according to which – there is possibility of invoking all particular phases of implementation in concrete order and building final implementation of our domain object – to make it possible and however consistent with scheme being built – composition builder contains method responsible for invoking particular builder and assigning the proper product (domain object) to it

Example of implementation in C#

Domain class:


    /// <summary>
    /// Main product class which instances will be created during the program.
    /// </summary>
    public class Product
    {
        /// <summary>
        /// Set of properties that will be partially set during creating Product's class instance.
        /// </summary>
        public string Property1 { get; private set; }
        public string Property2 { get; private set; }
        public string Property3 { get; private set; }

        /// <summary>
        /// The class constructor.
        /// </summary>
        /// <param name="property1">The property1.</param>
        /// <returns>Instance of Product.</returns>
        public static Product Create()
        {
            return new Product();
        }

        /// <summary>
        /// Sets Property1 value of the Product's instance.
        /// </summary>
        /// <param name="property1">The property1.</param>
        /// <returns>Partially initialized Product's instance.</returns>
        public Product SetProperty1(string property1)
        {
            this.Property1 = property1;

            return this;
        }

        /// <summary>
        /// Sets Property2 value of the Product's instance.
        /// </summary>
        /// <param name="property2">The property2.</param>
        /// <returns>partially initialized Product's instance.</returns>
        public Product SetProperty2 (string property2)
        {
            this.Property2 = property2;

            return this;
        }

        /// <summary>
        /// Sets Property3 value of the Product's instance.
        /// </summary>
        /// <param name="property3">The property3.</param>
        /// <returns>Partially initialized Product's instance.</returns>
        public Product SetProperty3(string property3)
        {
            this.Property3 = property3;

            return this;
        }
    }

Abstract class delivering implementation for particular builders:


    /// <summary>
    /// Abstract class that is base class for implementation of particular builders BuilderA and BuilderB.
    /// </summary>
    public abstract class BuilderAbstract
    {
        /// <summary>
        /// Product member delivers possibility of performing particular operations on it.
        /// </summary>
        /// <seealso cref="Builder.Product">
        protected Product Product { get; private set; }

        /// <summary>
        /// Creates new instance of Product class using its Create() creational method and assigns this instance to the Product property.
        /// </summary>
        /// <seealso cref="Builder.Product">
        public void CreateProduct()
        {
            Product = Product.Create();
        }

        /// <summary>
        /// Delivers Product property.
        /// </summary>
        /// <returns>The Product property.</returns>
        /// <seealso cref="Builder.Product">
        public Product GetProduct()
        {
            return this.Product;
        }

        /// <summary>
        /// Methods to be overrided.
        /// </summary>
        public abstract void BuildProperty1();
        public abstract void BuildProperty2();
        public abstract void BuildProperty3();
    }

First builder inheriting from above implemented builder abstract class and building domain class:


    /// <summary>
    /// First builder that inherits from BuilderAbstract and delivers building method
    /// </summary>
    /// <seealso cref="BuilderAbstract">
    public class BuilderA : BuilderAbstract
    {
        /// <summary>
        /// Sets Property1 of the Product instance.
        /// </summary>
        public override void BuildProperty1()
        {
            Product.SetProperty1("property1_A");
        }

        /// <summary>
        /// Sets Property2 of the Product instance.
        /// </summary>
        public override void BuildProperty2()
        {
            Product.SetProperty2("property2_A");
        }

        /// <summary>
        /// Sets Property2 of the Product instance.
        /// </summary>
        public override void BuildProperty3()
        {
            Product.SetProperty3("property3_A");
        }
    }

Second builder inheriting from above implemented builder abstract class and building domain class:


    /// <summary>
    /// Second builder that inherits from BuilderAbstract and delivers building method
    /// </summary>
    /// <seealso cref="BuilderAbstract"> 
    public class BuilderB : BuilderAbstract
    {
        /// <summary>
        /// Sets Property1 of the Product instance.
        /// </summary>
        public override void BuildProperty1()
        {
            Product.SetProperty1("property1_B");
        }

        /// <summary>
        /// Sets Property2 of the Product instance.
        /// </summary>
        public override void BuildProperty2()
        {
            Product.SetProperty2("property2_B");
        }

        /// <summary>
        /// Sets Property2 of the Product instance.
        /// </summary>
        public override void BuildProperty3()
        {
            Product.SetProperty3("property3_B");
        }
    }

Composition builder allowing to invoke set of builder’s methods and compose them into the creation of product domain class:


    /// <summary>
    /// Invokes whole composition process and allows to create Product's class instance.
    /// </summary>
    public class CompositionBuilder
    {
        /// <summary>
        /// BuilderAbstract property - member that delivers possibility of invoking particular building methods.
        /// </summary>
        public BuilderAbstract Builder { get; private set; }

        /// <summary>
        /// Sets given BuilderAbstract instance to the Builder property.
        /// </summary>
        /// <param name="builder">Instance of the particular builder class.</param>
        public void SetBuilderAbstract (BuilderAbstract builder)
        {
            this.Builder = builder;
        }

        /// <summary>
        /// Delivers Product's instance.
        /// </summary>
        /// <returns>Product's instance.</returns>
        public Product GetProduct()
        {
            return this.Builder.GetProduct();
        }

        /// <summary>
        /// Invokes all building methods deriving from particular builder class and creates Product's instance.
        /// </summary>
        public void BuildComposition()
        {
            this.Builder.BuildProperty1();
            this.Builder.BuildProperty2();
            this.Builder.BuildProperty3();
        }
    }

Example of invoking instance of product class using BuilderA builder


var compositionBuilder = new CompositionBuilder();
var builderA = new BuilderA();

compositionBuilder.SetBuilderAbstract(builderA);
compositionBuilder.Builder.CreateProduct();

var product = compositionBuilder.GetProduct();
compositionBuilder.BuildComposition();

Console.WriteLine(product);
Console.WriteLine(product.Property1);
Console.WriteLine(product.Property2);
Console.WriteLine(product.Property3);
Console.Read();

Get back to Home Page

1 komentarz do wpisu “Wzorzec projektowy Twoim wzorcem cz. 3. Budowniczy / Design Pattern is your pattern pt. 3. Builder

  1. Pingback: dotnetomaniak.pl

Dodaj komentarz