Ответ
Паттерн «Посетитель» — это поведенческий паттерн, который позволяет добавлять новые операции к объектам, не изменяя их классы. Он полезен, когда у вас есть сложная структура объектов (например, дерево AST — абстрактного синтаксического дерева) и вы хотите выполнять над ними разнородные операции (логирование, рендеринг, анализ).
Ключевая идея: Вы отделяете алгоритм от структуры объектов, на которых он работает. Новые операции добавляются путём создания нового класса «посетителя».
Основные компоненты:
IVisitor— интерфейс с методамиVisitдля каждого типа элемента в структуре.IElement— интерфейс элемента, который принимает посетителя (методAccept).- Конкретные элементы (Concrete Elements) — реализуют
Accept, вызывая соответствующий метод посетителя. - Конкретные посетители (Concrete Visitors) — реализуют интерфейс
IVisitor, определяя поведение для каждого типа элемента.
Пример на C#:
// Элементы
public interface IShape
{
void Accept(IShapeVisitor visitor);
}
public class Circle : IShape
{
public double Radius { get; set; }
public void Accept(IShapeVisitor visitor) => visitor.Visit(this);
}
public class Rectangle : IShape
{
public double Width { get; set; }
public double Height { get; set; }
public void Accept(IShapeVisitor visitor) => visitor.Visit(this);
}
// Посетитель
public interface IShapeVisitor
{
void Visit(Circle circle);
void Visit(Rectangle rectangle);
}
// Конкретная операция — вычисление площади
public class AreaCalculator : IShapeVisitor
{
public double TotalArea { get; private set; }
public void Visit(Circle circle)
{
TotalArea += Math.PI * circle.Radius * circle.Radius;
}
public void Visit(Rectangle rectangle)
{
TotalArea += rectangle.Width * rectangle.Height;
}
}
// Использование
List<IShape> shapes = new List<IShape> { new Circle { Radius = 5 }, new Rectangle { Width = 2, Height = 3 } };
var calculator = new AreaCalculator();
foreach (var shape in shapes)
{
shape.Accept(calculator);
}
Console.WriteLine($"Total area: {calculator.TotalArea}");
Преимущества:
- Открыт/закрыт: Легко добавить новую операцию (нового посетителя), не трогая классы элементов.
- Сбор родственных операций: Логика одной операции сосредоточена в одном классе посетителя.
Недостатки:
- Сложно добавить новый тип элемента, так как придётся обновить всех существующих посетителей.
- Посетитель может получить доступ к приватным полям элементов только если они ему «откроются» (например, через внутренний интерфейс).
Ответ 18+ 🔞
А, паттерн «Посетитель»! Ну это ж классика, блядь, как «Муму» у Тургенева — все знают, что грустно, но до конца не понимают, зачем он нужен, пока сами не наступите на эти грабли.
Смотри, представь себе: у тебя есть зоопарк объектов — ну там кружочки, квадратики, треугольники, всякая хуйня. И ты такой: «О, хочу посчитать площадь всей этой красоты!». А потом: «А теперь хочу вывести их в JSON!». А потом: «А щас я их всех в красный цвет покрашу!». И если для каждой такой операции лезть в каждый класс фигур и добавлять метод CalculateArea(), ToJson(), PaintRed() — это же пиздец, чувак. Классы распухнут, как жаба, и каждый раз их пересобирать.
Вот тут-то и выходит на сцену этот хитрожопый «Посетитель». Суть в том, чтобы вынести все эти операции наружу, в отдельные классы-посетители. А сами объекты должны научиться только одному — принимать этого посетителя, говорить ему: «На, я вот такой-то, делай со мной что хочешь».
Как это выглядит в коде, блядь:
- Интерфейс посетителя (
IVisitor). Это как список дел, которые гость может сделать с каждым типом хозяина. Для круга — одно, для прямоугольника — другое. - Интерфейс элемента (
IElement). Это наша фигура. У неё один ключевой метод —Accept(IVisitor v). Всё, что она делает — говорит: «Эй, посетитель, вот я кто, работай!». - Конкретные элементы (
Circle,Rectangle). Реализуют этотAcceptсамым тупым образом: просто вызывают у посетителя методVisit(this). Вся магия — не в них. - Конкретные посетители (
AreaCalculator,JsonExporter). Вот где вся соль! Это отдельные классы, которые знают, как именно работать с каждым типом фигуры. Хочешь новую операцию? Создаёшь нового посетителя, и ни один класс фигур даже не чихнёт.
Пример, как это в жизни:
// Вот наши упоротые фигурки. Они умеют только принимать гостей.
public class Circle : IShape
{
public double Radius { get; set; }
// Вся ответственность на посетителе. Сам я нихуя не делаю.
public void Accept(IShapeVisitor visitor) => visitor.Visit(this);
}
public class Rectangle : IShape
{
public double Width { get; set; }
public double Height { get; set; }
public void Accept(IShapeVisitor visitor) => visitor.Visit(this);
}
// А это наш первый гость — бухгалтер, который площадь считает.
public class AreaCalculator : IShapeVisitor
{
public double TotalArea { get; private set; }
// О, пришёл круг! Считаю его площадь по своей формуле.
public void Visit(Circle circle)
{
TotalArea += Math.PI * circle.Radius * circle.Radius;
}
// А, это прямоугольник! Считаю уже по-другому.
public void Visit(Rectangle rectangle)
{
TotalArea += rectangle.Width * rectangle.Height;
}
}
// Использование, ёпта!
List<IShape> shapes = new List<IShape> { new Circle { Radius = 5 }, new Rectangle { Width = 2, Height = 3 } };
var calculator = new AreaCalculator(); // Создаём бухгалтера
foreach (var shape in shapes)
{
// Каждая фигура говорит: «На, бухгалтер, я вот такая, делай что должен!»
shape.Accept(calculator);
}
Console.WriteLine($"Total area: {calculator.TotalArea}"); // Считает всё правильно!
Плюсы, блядь, очевидны:
- Не лезем в старые классы. Захотел новую операцию (например,
CheckCollision) — написал нового посетителяCollisionDetector, и ни одна старая фигура даже не узнает об этом. Принцип «открыт для расширений, закрыт для изменений» в действии, красота! - Вся логика операции в одном месте. Не раскидана по всем классам, а аккуратно упакована в одном классе посетителя. Хочешь понять, как считается площадь? Открывай
AreaCalculatorи читай.
Но и минусы, сука, есть, куда без них:
- Добавить новый тип элемента — пиздец. Захотел добавить
Triangle? Придётся бегать по всем существующим посетителям (AreaCalculator,JsonExporterи т.д.) и в каждом дописывать методVisit(Triangle triangle). Если посетителей много — это адская работа. - Доступ к приватным полям. Посетитель живёт снаружи класса, поэтому если ему нужны внутренние данные объекта, классу придётся их «выставить» (через публичные свойства или какой-нибудь internal интерфейс). Не всегда удобно.
Итог: Паттерн «Посетитель» — это оружие массового поражения для сложных структур (типа AST в компиляторах), где типов элементов много, они стабильны, а операций с ними нужно делать самых разных — и всё время добавлять новые. Для простых случаев это стрельба из пушки по воробьям, только себе ноги прострелишь. Используй с умом, а то наговнокодишь, блядь.