Design patterns are reusable templates that help us solve software design problems using best practices. In this way, they help us build applications using code that is easier to maintain, understand, reuse and test.

Processing…
Success!

What is this pattern for?

It allows us to separate the construction of a complex object from its representation. Thus, the same creation process can generate different representations.

Let’s follow the example of the previous articles in the series, i.e. create a new social network. We want to give users the possibility to write articles and share them with online publications. These articles can contain a title, blocks of textimages, and videos. Of course, these elements follow a certain order.

When a user is writing an article, he wants to see how it will look, both on our website and in the publications where they will send it. But our website will obviously display HTML elements and online publications use Markdown to display their articles.

Therefore, our preview tool has to convert the article (which is plain text) to the right format to see how it will look at its destination.

To do this, we could convert the article to the proper format in the preview tool’s code, but this presents a number of problems:

  • We mix the article draft’s reading logic with the article representation logic.
  • Every time we want to include a new format (pdf, epub, etc) we will have to modify the preview tool’s logic, making it difficult to maintain.
  • Imagine that some publications display the article with double spacing or different text fonts. Implementing all these variants will make the preview tool’s code very difficult to maintain.

The solution: create the article representation using the Builder pattern.

How does it work?

In this pattern, there are three participants: the director, the builder, and the product. The product is the object that will be created, in our case the article in the correct format. The builder is the object that describes how to create each of the parts of the article in the correct format. Finally, the director is the object that describes the construction process, i.e. the parts of the article and their order, but without going into details of the final format of the article. The resulting diagram will be as follows:

In this diagram, TextReader is the director. It is the part of the preview tool that will process part by part the plain text that the user has written to build a representation in the form of a formatted article.

class TextReader:

    def __init__(self) -> None:
        self._builder = None

    def set_builder(self, builder: ArticleBuilder) -> None:
        self._builder = builder

    def read_article(self) -> None:
        while next_block_available():
            article_block = read_next_block()       
        
            if is_title(article_block):
                self._builder.add_title(article_block)
            elif is_image(article_block):
                self._builder.add_image(article_block)
            elif is_video(article_block):
                self._builder.add_video(article_block)
            elif is_text(article_block):
                self._builder.add_text(article_block)
            else:
                raise NotImplementedError("Unknown part of an article.")

As you can see, the director is passed an instance of the builder, which implements the ArticleBuilder interface. Then, in its read_article() method, the director will process each of the plain text blocks of the draft that the user is writing, check what type of content it is, and delegate to its builder the details of how to incorporate it into the article in its proper format. The builder has to implement the following interface:

from abc import ABC, abstractmethod
from typing import String

class ArticleBuilder(ABC): 
   
    @property
    @abstractmethod
    def article(self) -> None:
        pass    
    
    @abstractmethod
    def add_title(self, title: String) -> None:
        pass

    @abstractmethod
    def add_image(self, href: String) -> None:
        pass    
    @abstractmethod
    def add_video(self, file: String) -> None:
        pass    
    @abstractmethod
    def add_text(self, text: String) -> None:
        pass

The director will interact with this general interface and it is the responsibility of the subclasses that implement it to translate the plain text to the appropriate format. As we said, we are going to convert the article to two formats, HTML and Markdown, so we need two different Builders.

class HtmlBuilder(ArticleBuilder):

    def __init__(self) -> None:
        self.article = ""    
    @property
    def article(self) -> String:
        return self.article    
    def add_title(self, title: String) -> None:
        self.article += f"<h1>{title}</h1>"

    def add_image(self, href: String) -> None:
        self.article += f"<img src={href}>"    
    def add_video(self, file: String) -> None:
        self.article += f"<video><source src={file}></video>"    
    def add_text(self, text: String) -> None:
        self.article += f"<p>{text}</p>"

class MarkdownBuilder(ArticleBuilder):

    def __init__(self) -> None:
        self.article = ""

    @property
    def article(self) -> String:
        return self.article    
    def add_title(self, title: String) -> None:
        self.article += f"#{title}"

    def add_image(self, href: String) -> None:
        self.article += f"![]({href})"    
    def add_video(self, file: String) -> None:
        self.article += f"[![]({file})]"    
    def add_text(self, text: String) -> None:
        self.article += text

And once this is done, all that remains is to create the director in the preview code, pass it the appropriate builder and get the article in the desired format:

if __name__ == "__main__":
    article_reader = TextReader()
    
    # Convierte el artículo a formato HTML.
    html_builder = HtmlBuilder()
    article_reader.set_builder(html_builder)
    article_reader.read_article()
    html_article = html_builder.article

    # Convierte el artículo a formato Markdown.
    markdown_builder = MarkdownBuilder()
    article_reader.set_builder(markdown_builder)
    markdown_builder.read_article()
    markdown_article = markdown_builder.article

Benefits

  • Reduces coupling. The builder object gives the director a common interface to build the article.
  • It allows us to vary the internal representation of the article, respecting the common interface of the ArticleBuilder class.
  • Article building and representation logic remain independent. The specific subclasses of ArticleBuilder will take care of the details of the representation.
  • It facilitates the creation of complex objects with interdependent parts. The manager only gets the object when its representation has been completed.

Write A Comment