I practiced design patterns so that I could write code that was conscious of design. Other Design Patterns will be released frequently.
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.
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-, +, |.
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.|*||/
/||*+- - - - - -+*||/
/|+- - - - - - - - - -+|/
/+- - - - - - - - - - - -+/
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
Recommended Posts