[PYTHON] Design Pattern #Decorator

I practiced design patterns so that I could write code that was conscious of design. Other Design Patterns will be released frequently.

Preface

The primary goal is to understand when, what, and how to use design patterns. (I'm new to Java or a statically typed language, and I don't have a long history of python, so I think there are some things that aren't like Pythonista. If you have any suggestions, please teach me.)

This time, the structural pattern Decorator.

What is Decorator?

By covering an existing object with a new Decorator object while maintaining a transparent interface (API), you can add or rewrite functions from the outside without directly touching the contents of existing functions and classes. It is also used as an alternative to class inheritance when extending an existing class.

The sample program created here is to display a decorative frame around the character string. The decorative frame here is written with the characters-, +, |.

Overall class diagram

Decorator

display.py


from abc import ABCMeta, abstractmethod


class Display(metaclass=ABCMeta):

    @abstractmethod
    def get_columns(self):
        pass

    @abstractmethod
    def get_rows(self):
        pass

    @abstractmethod
    def get_row_text(self):
        pass

    def show(self):
        for i in range(self.get_rows()):
            print(self.get_row_text(i))

The Display class is an abstract class that displays a multi-line character string.

Since get_columns, get_rows, and get_row_text are abstract methods, only the declaration is left to the subclass. Each role is to get_columns the number of horizontal characters, get_rows the number of vertical rows, and get_row_text the method to get the string of the specified row.

The show is a Template Method pattern that uses the abstract methods get_rows and get_row_text. It is a method to get the character string to be displayed by get_rows and get_row_text and display all the rows in the for loop.

string_display.py



from display import Display


class StringDisplay(Display):

    def __init__(self, string):
        self.__string = string

    def get_columns(self):
        return len(self.__string)

    def get_rows(self):
        return 1

    def get_row_text(self, row):
        if row == 0:
            return self.__string
        else:
            return None

The StringDisplay class is a class that displays a single line of text and is a subclass of the Display class. It implements the abstract method declared in the Display class. The string field holds the string to display. The StringDisplay class displays only one row of string field content, so get_columns returns the length of the string and get_rows returns 1. get_row_text returns a string field only if it takes the value of row 0.

border.py


from abc import ABCMeta
from display import Display


class Border(Display):

    __metaclass__ = ABCMeta

    def _border(self, display):
        self._display = display

The Border class is an abstract class that represents a "decorative frame". However, it is defined as a subclass of the Display class that displays strings. In other words, by inheritance ** the decorative frame has the same method as the contents **. The Border class inherits the get_columns, get_rows, get_row_text, and show methods.

side_border.py


from border import Border


class SideBorder(Border):

    def __init__(self, display, ch):
        self.display = display
        self.__border_char = ch

    def get_columns(self):
        return 1 + self.display.get_columns() + 1

    def get_rows(self):
        return self.display.get_rows()

    def get_row_text(self, row):
        return self.__border_char + \
            self.display.get_row_text(row) + \
            self.__border_char

The SideBorder class is a kind of concrete decorative frame and is a subclass of the Border class. The SideBorder class decorates the left and right sides of the character string with fixed characters (borderChar). And all the abstract methods declared in the superclass are implemented here.

get_columns is a method to get the number of characters next to the displayed characters. The number of characters is the number of characters in the "contents" that is wrapped around this decorative frame, plus the left and right decorative characters.

Since the SideBorder class does not modify the vertical direction, display.get_rows is the return value of the get_rows method.

The get_row_text method gets the string of the line specified by the argument. The return value is display.get_row_text (row) with the decorative character bodrder_char added to both sides of the content string.

full_border.py


from border import Border


class FullBorder(Border):

    def __init__(self, display):
        self.display = display

    def get_columns(self):
        return 1 + self.display.get_columns() + 1

    def get_rows(self):
        return 1 + self.display.get_rows() + 1

    def get_row_text(self, row):
        if row == 0:
            return '+' + self._make_line('-', self.display.get_columns()) + '+'
        elif row == self.display.get_rows() + 1:
            return '+' + self._make_line('-', self.display.get_columns()) + '+'
        else:
            return '|' + self.display.get_row_text(row - 1) + '|'

    def _make_line(self, ch, count):
        buf = []
        for i in range(0, count):
            buf.append(ch)
        return ' '.join(buf)

The FullBorder class, like the SideBorder class, is one of the Border subclasses. The FullBorder class is decorated on the top, bottom, left and right.

The make_line method is an auxiliary method that creates a string of specified characters in a row.

main.py


from string_display import StringDisplay
from side_border import SideBorder
from full_border import FullBorder


def main():
    b1 = StringDisplay('Hello, world')
    b2 = SideBorder(b1, '#')
    b3 = FullBorder(b2)
    b4 = SideBorder(
        FullBorder(
            FullBorder(
                SideBorder(
                    FullBorder(
                        StringDisplay('Hello.')
                    ), '*'
                )
            )
        ), '/'
    )

    b1.show()
    b2.show()
    b3.show()
    b4.show()

if __name__ == "__main__":
    main()

Execution result (though the shape is distorted ...)

Hello, world
#Hello, world#
+- - - - - - - - - - - - - -+
|#Hello, world#|
+- - - - - - - - - - - - - -+
/+- - - - - - - - - - - -+/
/|+- - - - - - - - - -+|/
/||*+- - - - - -+*||/
/||*|Hello.|*||/
/||*+- - - - - -+*||/
/|+- - - - - - - - - -+|/
/+- - - - - - - - - - - -+/

Summary

The Decorator pattern equates the decorative frame with the contents.

In the sample program, the identification is expressed where the Border class that represents the decorative frame is a subclass of the Display class that represents the contents. In other words, the Border class (subclasses of the Border class) has the same interface as the Display class that represents the contents.

Even if you use a frame to wrap the contents, the interface is not hidden, so the methods get_columns, get_rows, get_row_text, show can be seen by other classes. This is what the interface is called "transparent".

The more you wrap the Decorator pattern, the more features it adds. ** I was able to add features without changing what is wrapped. ** **

Python has decorators in the first place

reference

Recommended Posts

Design Pattern #Decorator
Design Pattern #Builder
Design Pattern #Adapter
Design Pattern #Observer
Design Pattern #Facade
Design Pattern #Strategy
Design Pattern #Singleton
Design Pattern #Proxy
Learn the design pattern "Decorator" in Python
Design Pattern #Factory Method
Decorator pattern in Java
Design Pattern #Template Method
Decorator 1
Decorator 2
Ore Ore Design Pattern: Glocal Variable
Decorator
Decorator
Python Design Pattern --Template method
[Gang of Four] Design pattern learning
GoF java design pattern rough summary
Learn the design pattern "Prototype" in Python
Learn the design pattern "Builder" in Python
[Gang of Four] Design pattern learning --Singleton
[Gang of Four] Design pattern learning --Visitor
Design pattern-Adapter
[Gang of Four] Design pattern learning --Mediator
Learn the design pattern "Flyweight" in Python
Learn the design pattern "Observer" in Python
Learn the design pattern "Memento" in Python
Learn the design pattern "Proxy" in Python
Learn the design pattern "Command" in Python
[Gang of Four] Design pattern learning --Iterator
MyHDL decorator
GoF design pattern from the problem 2. Structure
Learn the design pattern "Visitor" in Python
Learn the design pattern "Bridge" in Python
Learn the design pattern "Mediator" in Python
Design pattern-Iterator
[Gang of Four] Design pattern learning --Facade
[Gang of Four] Design pattern learning --Composite
[Gang of Four] Design pattern learning --Prototype
GoF design pattern from the problem 1. Generation
Learn the design pattern "Iterator" in Python
[Gang of Four] Design pattern learning --Memento
[Gang of Four] Design pattern learning --State
[Gang of Four] Design pattern learning --Interpreter
[Gang of Four] Design pattern learning --Builder
Learn the design pattern "Strategy" in Python
[Gang of Four] Design pattern learning --Bridge
Learn the design pattern "Composite" in Python
Learn the design pattern "Singleton" with Python
Learn the design pattern "State" in Python
Learn the design pattern "Adapter" in Python
[Gang of Four] Design pattern learning --Proxy
[Gang of Four] Design pattern learning --Strategy
[Gang of Four] Design pattern learning --Adapter
Learn the design pattern "Facade" with Python
[Gang of Four] Design pattern learning --Observer
[Gang of Four] Design pattern learning --Command
GoF design pattern from the problem 3. Behavior
[Gang of Four] Design pattern learning --Fly Weight