7.2. State

7.2.1. Rationale

  • EN: State

  • PL: Stan

  • Type: object

7.2.2. Use Cases

  • Changes based on class

  • Open/Close principle

  • Using polymorphism

7.2.3. Problem

  • Canvas object can behave differently depending on selected Tool

  • All behaviors are represented by subclass of the tool interface

from enum import Enum


class ToolType(Enum):
    SELECTION = 1
    BRUSH = 2
    ERASER = 3


class Canvas:
    _current_tool: ToolType

    def get_current_tool(self) -> ToolType:
        return self._current_tool

    def set_current_tool(self, tool: ToolType) -> None:
        self._current_tool = tool

    def mouse_down(self) -> None:
        if self._current_tool == ToolType.SELECTION:
            print('Selection icon')
        elif self._current_tool == ToolType.BRUSH:
            print('Brush icon')
        elif self._current_tool == ToolType.ERASER:
            print('Eraser icon')

    def mouse_down(self) -> None:
        if self._current_tool == ToolType.SELECTION:
            print('Draw dashed rectangle')
        elif self._current_tool == ToolType.BRUSH:
            print('Draw line')
        elif self._current_tool == ToolType.ERASER:
            print('Erase something')

7.2.4. Design

../../_images/designpatterns-state-gof.png

7.2.5. Implementation

../../_images/designpatterns-state-usecase.png
from abc import ABCMeta, abstractmethod


class Tool(metaclass=ABCMeta):
    @abstractmethod
    def mouse_down(self) -> None:
        pass

    @abstractmethod
    def mouse_up(self) -> None:
        pass


class SelectionTool(Tool):
    def mouse_down(self) -> None:
        print('Selection icon')

    def mouse_up(self) -> None:
        print('Draw dashed rectangle')


class BrushTool(Tool):
    def mouse_down(self) -> None:
        print('Brush icon')

    def mouse_up(self) -> None:
        print('Draw line')


class Canvas:
    __current_tool: Tool

    def mouse_down(self) -> None:
        self.__current_tool.mouse_down()

    def mouse_up(self) -> None:
        self.__current_tool.mouse_up()

    def get_current_tool(self):
        return self.__current_tool

    def set_current_tool(self, newtool: Tool):
        self.__current_tool = newtool


if __name__ == '__main__':
    canvas = Canvas()
    canvas.set_current_tool(SelectionTool())

    canvas.mouse_down()
    # Selection icon

    canvas.mouse_up()
    # Draw dashed rectangle

7.2.6. Assignments

Code 7.12. Solution
"""
* Assignment: DesignPatterns Behavioral State
* Complexity: medium
* Lines of code: 34 lines
* Time: 21 min

English:
    1. Implement State pattern
    2. Then add another language:
        a. Chinese hello: 你好
        b. Chinese goodbye: 再见
    3. Run doctests - all must succeed

Polish:
    1. Zaimplementuj wzorzec State
    2. Następnie dodaj nowy język:
        a. Chinese hello: 你好
        b. Chinese goodbye: 再见
    3. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> polish = Translation(Polish())
    >>> english = Translation(English())
    >>> chinese = Translation(Chinese())

    >>> polish.hello()
    'Cześć'
    >>> polish.goodbye()
    'Do widzenia'

    >>> english.hello()
    'Hello'
    >>> english.goodbye()
    'Goodbye'

    >>> chinese.hello()
    '你好'
    >>> chinese.goodbye()
    '再见'
"""
from enum import Enum


class Language(Enum):
    POLISH = 'pl'
    ENGLISH = 'en'
    RUSSIAN = 'ru'


class Translation:
    __language: Language

    def __init__(self, language: Language):
        self.__language = language

    def hello(self) -> str:
        if self.__language is Language.POLISH:
            return 'Cześć'
        elif self.__language is Language.ENGLISH:
            return 'Hello'
        elif self.__language is Language.RUSSIAN:
            return 'Здравствуй'
        else:
            return 'Unknown language'

    def goodbye(self) -> str:
        if self.__language is Language.POLISH:
            return 'Do widzenia'
        elif self.__language is Language.ENGLISH:
            return 'Goodbye'
        elif self.__language is Language.RUSSIAN:
            return 'До свидания'
        else:
            return 'Unknown language'