Описывается поведенческий шаблон проектирования Visitor, условия его применения, приводится пример реализации.
Содержание
- Применение
- Реализация
- Итоги
Применение
Шаблон проектирования Visitor используют в случаях, когда требуется в существующую систему классов добавить новую операцию, а менять код классов не хочется. Например, вы создали идеальную иерархию. Но тут приходит менеджер и просит, чтобы ее элементы умели делать еще что-то, что никак не вписывается в разработанную вами идеальную концепцию, да и кому захочется ковырять отлаженный код.
В этом случае шаблон проектирования Visitor предлагает создать отдельный класс, который и будет выполнять новую операцию, надо только передать ему в качестве параметра конкретный класс иерархии. У этого нового класса, назовём его Посетитель, при этом должны быть методы с новой операцией для каждого элемента вашей системы. Всё вроде хорошо, есть компонент, реализующий новую операцию для каждого объекта существующей системы.
Но есть одна проблема. Как Посетителю определить, метод для объекта какого класса вызывать? Полиморфизм использовать не получится, классы то разные. Чтобы выбрать нужный метод, можно, конечно, определять тип входного объекта. Но это длинный switch или множество if ‘ов, что не есть хорошо. В шаблоне Visitor проблема разрешается следующим образом. В классы существующей иерархии вводится метод для приема Посетителя, а класс уже знает, какой метод Посетителя вызвать.
Удобно изначально в вашу иерархию классов ввести метод для приема Посетителя. Тогда это будет выглядеть примерно так.
//интерфейс, обеспечивающий обработку новой операции Посетителя
interface IComponent
{
//какие-то обязательные методы
//метод, который будет принимать
public function accept(IVisitor $visitor): void;
}
//компонент существующей системы классов
class ComponentA implements IComponent
{
//методы компонентa
public function accept(IVisitor $visitor): void
{
$visitor->operationForComponentA($this);
}
}
Так что, увы, в классы вашей иерархии все-таки придется добавить метод для приема Посетителя. Но эта доработка минимальна и не требует модификации существующего кода. Зачастую нет худа без добра. Вводимый метод accept() для приема Посетителя принимает в качестве параметра интерфейс. Поэтому для реализации еще одной новой операции на стороне надо будет лишь создать новый класс Посетителя и передавать его элементам вашей иерархии в существующий метод для приема Посетителя.
Реализация
Итак, для реализации шаблона проектирования Visitor нам понадобятся:
- IComponent — интерфейс существующей системы классов;
- ComponentA — класс компонента А системы;
- ComponentB — класс компонента В системы;
- IVisitor — интерфейс, определяющий поведение класса Visitor;
- Visitor1 — класс Посетителя, реализующий новую операцию компонентов существующей системы.
- Visitor2 — класс Посетителя, реализующий ещё одну новую операцию компонентов существующей системы.
interface IComponent
{
//обязательные методы компонентов
//метод, который будет принимать Посетителя
public function accept(IVisitor $visitor): void;
}
class ComponentA implements IComponent
{
public function run(): void
{
echo "Работает ComponentA\n";
}
public function accept(IVisitor $visitor): void
{
$visitor->operationForComponentA($this);
}
}
class ComponentB implements IComponent
{
public function run(): void
{
echo "Работает ComponentB\n";
}
public function accept(IVisitor $visitor): void
{
$visitor->operationForComponentB($this);
}
}
В интерфейсе Посетителя должны быть объявлены методы реализации вновь вводимой операции для всех компонентов. В нашем случае таких компонентов два.
interface IVisitor
{
public function operationForComponentA(ComponentA $component): void;
public function operationForComponentB(ComponentB $component): void;
}
class Visitor1 implements IVisitor
{
public function operationForComponentA(ComponentA $component): void
{
echo "Выполнение новой операции1 для компонента A\n";
}
public function operationForComponentB(ComponentB $component): void
{
echo "Выполнение новой операции1 для компонента B\n";
}
}
Как указывалось выше, при добавлении ещё одной новой операции для компонентов существующей системы с помощью шаблона Visitor достаточно создать класс нового посетителя.
class Visitor2 implements IVisitor
{
public function operationForComponentA(ComponentA $component): void
{
echo "Выполнение новой операции2 для компонента A\n";
}
public function operationForComponentB(ComponentB $component): void
{
echo "Выполнение новой операции2 для компонента B\n";
}
}
Все вместе будет работать примерно так.
//массив любых объектов, в которых реализован интерфейс IVisitor
$components = [
new ComponentA(),
new ComponentB(),
];
$visitor1 = new Visitor1();
$visitor2 = new Visitor2();
executeOperation($components, $visitor1);
executeOperation($components, $visitor2);
//Функция выполнения операции
function executeOperation(array $components, IVisitor $visitor): void
{
foreach ($components as $component) {
$component->accept($visitor);
}
}
UML диаграмма классов шаблона Visitor будет выглядеть так.
Итоги
Итак, был рассмотрен поведенческий шаблон проектирования Visitor, позволяющий дополнять существующую систему классов новым поведением с минимальными изменениями кода. Шаблон будет полезным в случаях, когда:
- внедрение в существующую бизнес-логику новай функциональности требует значительной доработки кода;
- необходимо выполнить какие-либо операции на объектах разных классов сложной структуры данных;
- для разных элементов структуры операции должны применяться по-разному;
К недостаткам шаблона следует отнести:
- требуется доступ к коду изменяемых элементов;
- для каждого объекта системы требуется свой метод в классе Visitor, поэтому при расширении типов, потребуется добавлять новый метод в Посетителе.
Перейти к списку шаблонов проектирования.
