Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Visitor и Double Dispatch
visitor

Visitor и Double Dispatch

Операции над объектами без изменения классов. Double dispatch через singledispatch. Pattern matching Python 3.10+.

Visitor и Double Dispatch

Visitor добавляет операции без изменения классов. Double Dispatch выбирает метод по типу получателя и аргумента.


#Visitor: операции над объектами

Visitor — поведенческий паттерн, позволяющий добавлять операции к объектам без изменения их классов через двойную диспетчеризацию.

#Проблема: нужно добавить операцию

# ❌ ПЛОХО: Нужно менять каждый класс для новой операции class Circle: def __init__(self, radius: float): self.radius = radius def draw(self): print(f"Drawing circle r={self.radius}") class Square: def __init__(self, side: float): self.side = side def draw(self): print(f"Drawing square side={self.side}") # Нужно добавить export to XML — меняем все классы! class Circle: # ... предыдущий код ... def to_xml(self) -> str: return f"<circle radius='{self.radius}'/>" class Square: # ... предыдущий код ... def to_xml(self) -> str: return f"<square side='{self.side}'/>" # Теперь нужно добавить area() — снова меняем все классы!

#Решение: Visitor

# ✅ ХОРОШО: Операции в отдельных классах-посетителях from abc import ABC, abstractmethod class Shape(ABC): @abstractmethod def accept(self, visitor: "ShapeVisitor"): pass class Circle(Shape): def __init__(self, radius: float): self.radius = radius def accept(self, visitor: "ShapeVisitor"): visitor.visit_circle(self) def draw(self): print(f"Drawing circle r={self.radius}") class Square(Shape): def __init__(self, side: float): self.side = side def accept(self, visitor: "ShapeVisitor"): visitor.visit_square(self) def draw(self): print(f"Drawing square side={self.side}") class ShapeVisitor(ABC): @abstractmethod def visit_circle(self, circle: Circle): pass @abstractmethod def visit_square(self, square: Square): pass class DrawVisitor(ShapeVisitor): def visit_circle(self, circle: Circle): circle.draw() def visit_square(self, square: Square): square.draw() class XMLExportVisitor(ShapeVisitor): def __init__(self): self.xml_parts = [] def visit_circle(self, circle: Circle): self.xml_parts.append(f"<circle radius='{circle.radius}'/>") def visit_square(self, square: Square): self.xml_parts.append(f"<square side='{square.side}'/>") def get_xml(self) -> str: return "".join(self.xml_parts) class AreaVisitor(ShapeVisitor): def __init__(self): self.total_area = 0 def visit_circle(self, circle: Circle): area = 3.14159 * circle.radius ** 2 print(f"Circle area: {area:.2f}") self.total_area += area def visit_square(self, square: Square): area = square.side ** 2 print(f"Square area: {area:.2f}") self.total_area += area # Использование shapes = [Circle(5), Square(4), Circle(3)] # Рисование draw_visitor = DrawVisitor() for shape in shapes: shape.accept(draw_visitor) # Экспорт в XML xml_visitor = XMLExportVisitor() for shape in shapes: shape.accept(xml_visitor) print(xml_visitor.get_xml()) # <circle radius='5'/><square side='4'/><circle radius='3'/> # Площадь area_visitor = AreaVisitor() for shape in shapes: shape.accept(area_visitor) print(f"Total area: {area_visitor.total_area:.2f}")

#Double Dispatch: двойная диспетчеризация

#Проблема: одинарная диспетчеризация

# ❌ ПЛОХО: Выбор метода только по типу получателя class Collision: def collide(self, other: "GameObject"): # Не знаем тип other statically pass class Player(Collision): def collide(self, other: "GameObject"): # Приходится проверять тип в runtime if isinstance(other, Enemy): self._collide_with_enemy(other) elif isinstance(other, Wall): self._collide_with_wall(other) def _collide_with_enemy(self, enemy): print("Player hits Enemy") def _collide_with_wall(self, wall): print("Player hits Wall") # Проблема: логика столкновений размазана

#Решение: Double Dispatch

# ✅ ХОРОШО: Выбор по типу обоих объектов from abc import ABC, abstractmethod class GameObject(ABC): @abstractmethod def collide(self, other: "GameObject"): pass @abstractmethod def collide_with_player(self, player: "Player"): pass @abstractmethod def collide_with_enemy(self, enemy: "Enemy"): pass @abstractmethod def collide_with_wall(self, wall: "Wall"): pass class Player(GameObject): def collide(self, other: GameObject): # Первый dispatch: по типу other other.collide_with_player(self) def collide_with_player(self, other: "Player"): print("Player hits Player") def collide_with_enemy(self, enemy: "Enemy"): print("Player hits Enemy") def collide_with_wall(self, wall: "Wall"): print("Player hits Wall") class Enemy(GameObject): def collide(self, other: GameObject): other.collide_with_enemy(self) def collide_with_player(self, player: "Player"): print("Enemy hits Player") def collide_with_enemy(self, other: "Enemy"): print("Enemy hits Enemy") def collide_with_wall(self, wall: "Wall"): print("Enemy hits Wall") class Wall(GameObject): def collide(self, other: GameObject): other.collide_with_wall(self) def collide_with_player(self, player: "Player"): print("Wall hits Player") def collide_with_enemy(self, enemy: "Enemy"): print("Wall hits Enemy") def collide_with_wall(self, other: "Wall"): print("Wall hits Wall") # Использование player = Player() enemy = Enemy() wall = Wall() player.collide(enemy) # Player hits Enemy enemy.collide(player) # Enemy hits Player player.collide(wall) # Player hits Wall

#functools.singledispatch

#Одинарная диспетчеризация по типу

# ✅ singledispatch для функций from functools import singledispatch @singledispatch def process(value): """Базовая реализация""" print(f"Processing generic: {value}") @process.register(int) def _(value: int): print(f"Processing int: {value * 2}") @process.register(str) def _(value: str): print(f"Processing str: {value.upper()}") @process.register(list) def _(value: list): print(f"Processing list: {len(value)} items") # Использование process(42) # Processing int: 84 process("hello") # Processing str: HELLO process([1, 2, 3]) # Processing list: 3 items process(3.14) # Processing generic: 3.14

#singledispatchmethod для методов

# ✅ singledispatchmethod для методов from functools import singledispatchmethod class Processor: @singledispatchmethod def process(self, value): return f"Generic: {value}" @process.register def _(self, value: int): return f"Integer: {value * 2}" @process.register def _(self, value: str): return f"String: {value.upper()}" # Использование p = Processor() print(p.process(42)) # Integer: 84 print(p.process("hi")) # String: HI print(p.process(3.14)) # Generic: 3.14

#Pattern matching (Python 3.10+)

# ✅ Pattern matching как альтернатива Visitor from abc import ABC class Shape(ABC): pass class Circle(Shape): def __init__(self, radius: float): self.radius = radius class Square(Shape): def __init__(self, side: float): self.side = side def draw(shape: Shape): match shape: case Circle(radius=r): print(f"Drawing circle r={r}") case Square(side=s): print(f"Drawing square side={s}") case _: print("Unknown shape") def area(shape: Shape) -> float: match shape: case Circle(radius=r): return 3.14159 * r ** 2 case Square(side=s): return s ** 2 case _: return 0 # Использование shapes = [Circle(5), Square(4)] for shape in shapes: draw(shape) total = sum(area(s) for s in shapes) print(f"Total area: {total:.2f}")

#Практическое применение

#AST обход

# ✅ Visitor для AST from abc import ABC, abstractmethod class ASTNode(ABC): @abstractmethod def accept(self, visitor: "ASTVisitor"): pass class NumberNode(ASTNode): def __init__(self, value: float): self.value = value def accept(self, visitor: "ASTVisitor"): visitor.visit_number(self) class AddNode(ASTNode): def __init__(self, left: ASTNode, right: ASTNode): self.left = left self.right = right def accept(self, visitor: "ASTVisitor"): visitor.visit_add(self) class ASTVisitor(ABC): @abstractmethod def visit_number(self, node: NumberNode): pass @abstractmethod def visit_add(self, node: AddNode): pass class EvalVisitor(ASTVisitor): def visit_number(self, node: NumberNode): return node.value def visit_add(self, node: AddNode): node.left.accept(self) node.right.accept(self) return self.left_result + self.right_result # Упрощённая версия def evaluate(self, node: ASTNode) -> float: if isinstance(node, NumberNode): return node.value elif isinstance(node, AddNode): return self.evaluate(node.left) + self.evaluate(node.right) # Использование: 2 + 3 ast = AddNode(NumberNode(2), NumberNode(3)) visitor = EvalVisitor() print(visitor.evaluate(ast)) # 5

#Отчётность по сотрудникам

# ✅ Visitor для отчётов from abc import ABC, abstractmethod class Employee(ABC): @abstractmethod def accept(self, visitor: "EmployeeVisitor"): pass class Developer(Employee): def __init__(self, name: str, lines: int): self.name = name self.lines = lines def accept(self, visitor: "EmployeeVisitor"): visitor.visit_developer(self) class Manager(Employee): def __init__(self, name: str, team_size: int): self.name = name self.team_size = team_size def accept(self, visitor: "EmployeeVisitor"): visitor.visit_manager(self) class EmployeeVisitor(ABC): @abstractmethod def visit_developer(self, dev: Developer): pass @abstractmethod def visit_manager(self, mgr: Manager): pass class SalaryVisitor(EmployeeVisitor): def __init__(self): self.total = 0 def visit_developer(self, dev: Developer): salary = dev.lines * 0.1 print(f"{dev.name}: ${salary}") self.total += salary def visit_manager(self, mgr: Manager): salary = mgr.team_size * 1000 print(f"{mgr.name}: ${salary}") self.total += salary # Использование employees = [ Developer("Alice", 5000), Manager("Bob", 5), Developer("Charlie", 7000), ] visitor = SalaryVisitor() for emp in employees: emp.accept(visitor) print(f"Total salary: ${visitor.total}")

#Что дальше?

Изучите тему Repository и Unit of Work для абстракции доступа к данным.

Далее: Repository и Unit of Work