mercredi 19 avril 2023

Visitor pattern without double dispatch

Recently I ran across a code snippet that demonstrated a modified visitor pattern without double dispatch.

They used the run time class to compose the visit function name, which allowed them to move the accept function to the base class of the data classes, and then later to replace it completely by adding a generic visit function in the visitor.

I suppose the same thing could be done in Java and C# using reflection.

What are the advantages and disadvantages of such an approach?

"""Example of a modified visitor design pattern using runtime info instead of double dispatch"""
from __future__ import annotations # enable postponed evaluation for cyclic reference classes
from abc import ABC
from abc import abstractmethod
from typing import List

class CursorVisitor(ABC):
    def visit(self, cursor: ICursor) -> None:
        visit_func_name = f"visit_{type(cursor).__name__}"
        visit_func = getattr(self, visit_func_name)
        visit_func(cursor)        
    @abstractmethod
    def visit_Cursor2D(self, cursor: Cursor2D) -> None:
        pass
    @abstractmethod
    def visit_Cursor3D(self, cursor: Cursor3D) -> None:
        pass

class ICursor(ABC):
    @abstractmethod
    def print(self) -> None:
        pass

class Cursor2D(ICursor):
    def __init__(self, x: int, y: int) -> None:
        self._x = x
        self._y = y
    def move(self, dx: int, dy: int) -> None:
        self._x += dx
        self._y += dy
    def print(self) -> None:
        print("Cursor2D(", self._x, ",", self._y, ")")

class Cursor3D(ICursor):
    def __init__(self, x: int, y: int, z:int) -> None:
        self._x = x
        self._y = y
        self._z = z
    def move(self, dx: int, dy: int, dz: int) -> None:
        self._x += dx
        self._y += dy
        self._z += dz
    def print(self) -> None:
        print("Cursor3D(", self._x, ",", self._y, ",", self._z,")")

class CursorMoveVisitor(CursorVisitor):
    def __init__(self, dx: int, dy: int, dz: int) -> None:
        self._dx = dx
        self._dy = dy
        self._dz = dz
    def visit_Cursor2D(self, cursor: Cursor2D) -> None:
        cursor.move(self._dx, self._dy)
    def visit_Cursor3D(self, cursor: Cursor3D) -> None:
        cursor.move(self._dx, self._dy, self._dz)

class CursorPrintVisitor(CursorVisitor):
    def visit_Cursor2D(self, cursor: Cursor2D) -> None:
        cursor.print()
    def visit_Cursor3D(self, cursor: Cursor3D) -> None:
        cursor.print()

def demo_use_of_visitor():
    cursor_list: List[ICursor] = []
    cursor_list.append(Cursor2D(5,5))
    cursor_list.append(Cursor3D(6,6,7))
    for cursor in cursor_list:
        move_visitor:CursorMoveVisitor = CursorMoveVisitor(1,1,1)
        move_visitor.visit(cursor)
        print_visitor:CursorPrintVisitor = CursorPrintVisitor()
        print_visitor.visit(cursor)



demo_use_of_visitor()

Aucun commentaire:

Enregistrer un commentaire