Design Patters: Builder

Credit: Concepts explained here are based on a book, “増補改訂版Java言語で学ぶデザインパターン入門”. 

A builder pattern is a pattern in which an instance is created by calling a class which in turn calls an interface which is a framework for objects to be built.

Let’s create a sample program in which a document is created. A document is created by calling a Director class which calls a Builder interface. TextBuilder and HTMLBuilder classes implement a Builder interface.

A Builder class has three abstract methods which make title, strings and items of lists.

# builder.py
from abc import ABC, abstractmethod

class Builder(ABC):
    @abstractmethod
    def make_title(self, title):
        pass
    @abstractmethod
    def make_string(self, sting):
        pass
    @abstractmethod
    def make_items(self, items):
        pass
    @abstractmethod
    def close(self):
        pass

A Director class has a builder instance as an attribute by which title, string and items are created. A builder instance given to the constructor is an instance of a subclass of a Builder class that implements concrete algorithm for each abstract method.

# director.py
class Director:
    def __init__(self, builder):
        self._builder = builder

    def construct(self):
        self._builder.make_title("Greeting")
        self._builder.make_string("Moring to Afternoon")
        self._builder.make_items(["Good Morning", "Good afternoon"])
        self._builder.make_string("At nignt")
        self._builder.make_items(["Good evening", "Good night", "Goodbye"])
        self._builder.close()

A TextBuilder class is a subclass of a Builder class. This class creates a text from given strings.

# text_builder.py
from builder import Builder
from string_buffer import StringBuffer

class TextBuilder(Builder):
    def __init__(self):
        self._buffer = StringBuffer()
    def make_title(self, title):
        self._buffer.append("==============================\n")
        self._buffer.append(f"[     {title}      ]\n")
        self._buffer.append("\n")
    
    def make_string(self, string):
        self._buffer.append(f"■ {string} \n")
        self._buffer.append(f"\n")

    def make_items(self, items):
        for i in range(len(items)):
            self._buffer.append(f" ・{items[i]}\n" )
        self._buffer.append("\n")
    
    def close(self):
        self._buffer.append("==============================\n")

    def get_result(self):
        return self._buffer.to_string()

A HTMLBuilder is a subclass of a Builder class. This class creates a HTML file from given strings.

# html_builder.py 
from builder import Builder

class HTMLBuilder(Builder):
    def __init__(self):
        self._filename = None
        self._writer = None

    def make_title(self, title):
        self._filename = title + ".html"
        self._writer = open(self._filename, "w")
        self._writer.write(f"<html><head><title>{title}</title></head><body>\n")
        self._writer.write(f"<h1>{title}</h1>\n")
    
    def make_string(self, string):
        self._writer.write(f"<p>{string}</p>\n")
    
    def make_items(self, items):
        self._writer.write("<ul>")
        for i in range(len(items)):
            self._writer.write(f"<li>{items[i]}</li>\n")
        self._writer.write("</ul>\n")

    def close(self):
        self._writer.write("</body></html>\n")
        self._writer.close()
    
    def get_result(self):
        return self._filename

A StringBuffer is a helper class which holds a list of strings and converts it to a text.

# string_buffer.py
class StringBuffer:
    def __init__(self):
         self._strings = []
    
    def append(self, string):
        self._strings.append(string)
    
    def to_string(self):
        return " ".join(self._strings)

Let’s run this program.

# main.py
from director import Director
from text_builder import TextBuilder
from html_builder import HTMLBuilder


def main(text_kind):
    if text_kind == "plain":
        print("Create a plain text.")
        text_builder = TextBuilder()
        director = Director(text_builder)
        director.construct()
        result = text_builder.get_result()
        print(result)
    elif text_kind == "html":
        print("Create a html file.")
        html_builder = HTMLBuilder()
        director = Director(html_builder)
        director.construct()
        filename = html_builder.get_result()
        print(f"{filename} is created.")
    else:
        print("No argument is passed.")

if __name__ == "__main__":
    main("plain")
    main("html")
# Result
Create a plain text.
==============================
 [     Greeting      ]
 
 ■ Moring to Afternoon 
 
  ・Good Morning
  ・Good afternoon
 
 ■ At nignt 
 
  ・Good evening
  ・Good night
  ・Goodbye
 
 ==============================
Create a html file.
Greeting.html is created.
<!-- Greeting.html -->
<html><head><title>Greeting</title></head><body>
<h1>Greeting</h1>
<p>Moring to Afternoon</p>
<ul><li>Good Morning</li>
<li>Good afternoon</li>
</ul>
<p>At nignt</p>
<ul><li>Good evening</li>
<li>Good night</li>
<li>Goodbye</li>
</ul>
</body></html>

One benefit of a Builder pattern: It makes it possible to create an object, a document in the sample program without modifying a Director or a Builder class. In this example, since a Director class does not know the instance of a subclass of a Builder class is, it is possible to modify a subclass without affecting other classes.

Leave a comment