6.10. Visitor

  • EN: Visitor

  • PL: Odwiedzający

  • Type: object

6.10.1. Pattern

  • Add new operations to an object structure without modifying it

  • For building editors

  • Open/Close Principle

../../_images/designpatterns-visitor-pattern.png

6.10.2. Problem

design-patterns/behavioral/img/designpatterns-visitor-problem.png


6.10.3. Solution

../../_images/designpatterns-visitor-solution.png

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


class HtmlNode(ABC):
    @abstractmethod
    def execute(self, operation: 'Operation') -> None:
        pass

class HeadingNode(HtmlNode):
    def execute(self, operation: 'Operation') -> None:
        operation.apply_heading(self)

class AnchorNode(HtmlNode):
    def execute(self, operation: 'Operation') -> None:
        operation.apply_anchor(self)


class Operation(ABC):
    """Visitor"""

    @abstractmethod
    def apply_heading(self, heading: HeadingNode) -> None:
        pass

    @abstractmethod
    def apply_anchor(self, anchor: AnchorNode) -> None:
        pass

class HighlightOperation(Operation):
    def apply_heading(self, heading: HeadingNode) -> None:
        print('highlight-heading')

    def apply_anchor(self, anchor: AnchorNode) -> None:
        print('apply-anchor')

class PlaintextOperation(Operation):
    def apply_heading(self, heading: HeadingNode) -> None:
        print('text-heading')

    def apply_anchor(self, anchor: AnchorNode) -> None:
        print('text-anchor')


@dataclass
class HtmlDocument:
    nodes: list[HtmlNode] = field(default_factory=list)

    def add(self, node: HtmlNode) -> None:
        self.nodes.append(node)

    def execute(self, operation: Operation) -> None:
        for node in self.nodes:
            node.execute(operation)


if __name__ == '__main__':
    document = HtmlDocument()
    document.add(HeadingNode())
    document.add(AnchorNode())
    document.execute(PlaintextOperation())


# class Operation:
#     @abstractmethod
#     @singledispatchmethod
#     def apply(arg):
#         raise NotImplementedError('Argument must be HtmlNode')
#
#     @abstractmethod
#     @apply.register
#     def _(self, heading: HeadingNode):
#         pass
#
#     @abstractmethod
#     @apply.register
#     def _(self, anchor: AnchorNode):
#         pass

6.10.4. Assignments