8.8. Mediator

8.8.1. Rationale

  • EN: Mediator

  • PL: Mediator

  • Type: object

8.8.2. Use Cases

  • Input fields which needs to collaborate

  • Cannot submit form if all required fields are not filled

  • If you select article in list of articles, editor form with current article content and title gets populated

  • Auto slug-field based on title content

8.8.3. Design

../_images/designpatterns-mediator-gof.png

8.8.4. Problem

../_images/designpatterns-mediator-problem.png

8.8.5. Implementation

../_images/designpatterns-mediator-usecase-1.png
../_images/designpatterns-mediator-usecase-2.png
from abc import ABCMeta, abstractmethod
from dataclasses import dataclass


class DialogBox(metaclass=ABCMeta):
    """Mediator class"""
    @abstractmethod
    def changed(self, control: 'UIControl') -> None:
        pass


@dataclass
class UIControl(metaclass=ABCMeta):
    _owner: DialogBox


class ListBox(UIControl):
    __selection: str

    def __init__(self, owner: DialogBox) -> None:
        super().__init__(owner)

    def get_selection(self) -> str:
        return self.__selection

    def set_selection(self, selection: str) -> None:
        self.__selection = selection
        self._owner.changed(self)


class TextBox(UIControl):
    __content: str

    def __init__(self, owner: DialogBox) -> None:
        super().__init__(owner)

    def get_content(self) -> str:
        return self.__content

    def set_content(self, content: str) -> None:
        self.__content = content
        self._owner.changed(self)


class Button(UIControl):
    __enabled: bool

    def __init__(self, owner: DialogBox) -> None:
        super().__init__(owner)

    def set_enabled(self, enabled: bool) -> None:
        self.__enabled = enabled

    def is_enabled(self) -> bool:
        self._owner.changed(self)
        return self.__enabled


class ArticlesDialogBox(DialogBox):
    __articles_listbox: ListBox
    __title_textbox: TextBox
    __save_button: Button

    def simulate_user_interaction(self) -> None:
        self.__articles_listbox.set_selection('Article 1')
        self.__title_textbox.set_content('')
        self.__title_textbox.set_content('Article 2')
        print(f'Text box: {self.__title_textbox.get_content()}')
        print(f'Button: {self.__save_button.is_enabled()}')

    def __init__(self) -> None:
        self.__articles_listbox = ListBox(self)
        self.__title_textbox = TextBox(self)
        self.__save_button = Button(self)

    def changed(self, control: 'UIControl') -> None:
        if control == self.__articles_listbox:
            self.__article_selected()
        elif control == self.__title_textbox:
            self.__title_changed()

    def __article_selected(self) -> None:
        self.__title_textbox.set_content(self.__articles_listbox.get_selection())
        self.__save_button.set_enabled(True)

    def __title_changed(self) -> None:
        content = self.__title_textbox.get_content()
        is_empty = (content == None or content == '')
        self.__save_button.set_enabled(not is_empty)


if __name__ == '__main__':
    dialog = ArticlesDialogBox()
    dialog.simulate_user_interaction()

Mediator with Observer Pattern:

from abc import ABCMeta, abstractmethod
from dataclasses import dataclass, field


class EventHandler(metaclass=ABCMeta):
    @abstractmethod
    def __call__(self) -> None:
        pass


@dataclass
class UIControl(metaclass=ABCMeta):
    __observers: list[EventHandler] = field(default_factory=list)

    def add_event_handler(self, observer: EventHandler) -> None:
        self.__observers.append(observer)

    def _notify_event_handlers(self):
        for observer in self.__observers:
            observer.__call__()


class ListBox(UIControl):
    __selection: str

    def get_selection(self) -> str:
        return self.__selection

    def set_selection(self, selection: str) -> None:
        self.__selection = selection
        self._notify_event_handlers()


class TextBox(UIControl):
    __content: str

    def get_content(self) -> str:
        return self.__content

    def set_content(self, content: str) -> None:
        self.__content = content
        self._notify_event_handlers()


class Button(UIControl):
    __enabled: bool

    def set_enabled(self, enabled: bool) -> None:
        self.__enabled = enabled
        self._notify_event_handlers()

    def is_enabled(self) -> bool:
        return self.__enabled


@dataclass
class ArticlesDialogBox:
    __articles_listbox: ListBox = ListBox()
    __title_textbox: TextBox = TextBox()
    __save_button: Button = Button()

    def __post_init__(self):
        self.__articles_listbox.add_event_handler(self.__article_selected)
        self.__title_textbox.add_event_handler(self.__title_changed)

    def simulate_user_interaction(self) -> None:
        self.__articles_listbox.set_selection('Article 1')
        self.__title_textbox.set_content('')
        self.__title_textbox.set_content('Article 2')
        print(f'Text box: {self.__title_textbox.get_content()}')
        print(f'Button: {self.__save_button.is_enabled()}')

    def __article_selected(self) -> None:
        self.__title_textbox.set_content(self.__articles_listbox.get_selection())
        self.__save_button.set_enabled(True)

    def __title_changed(self) -> None:
        content = self.__title_textbox.get_content()
        is_empty = (content == None or content == '')
        self.__save_button.set_enabled(not is_empty)


if __name__ == '__main__':
    dialog = ArticlesDialogBox()
    dialog.simulate_user_interaction()

8.8.6. Assignments

Todo

Create assignments

  • Form with Username and Password

  • If username and password is provided enable login button