Pull to refresh

Comments 17

Вообще говоря Visitor нужен не просто как аляповатая замена instanceof а для обнаружения ошибок при рефакторинге.
А именно: в случае добавления/удаления классов все контракты просто перестают компилироваться в отличие от instanceof-проверок.

Всегда рассматривал это как побочный эффект. Но, пожалуй, тоже можно записать в плюс шаблона.
Если же использовать sealed class в when реализации, как приведено тут, то тоже будет валиться с ошибкой компиляции, как и в классическом Visitor. Конечно, не всегда sealed удобен.

Вероятно вы путаете вполне себе жёстко определённый Visitor с более мягкими Dispatch
Как раз sealed классы отлично покрывают данный случай.
Почему-то сам автор не подчеркнул этого.
из-за модерации коментарий устарел
По-моему, если бы вы сделали класс
interface Visitor {
    fun visitHuman(creature: LivingCreature)
    fun visitAnimal(creature: LivingCreature)
}

то стало бы понятно, что преимущество шаблона посетитель в том, что вы не зависите от конкретных классов. И экземпляр Human, если ему вдруг захочется, может сам решить — мяукать ему или разговаривать))

в этом случае нет проверки на тип аргумента и возможна ошибка передачи типа visitHuman( Cat() )
А даже без ошибки программиста пришлось бы приводить тип аргумента, что является плохой практикой и классичиский Visitor ее устраняет.
Экземпляр Human может решать, мяукать или разговаривать, но речь идет о функционале вне Human, потому что с помощью Visitor мы реализовываем то, что не относится к обязанностям объекта. Нам нужны Visitor для того, чтобы оригинальный объект не инкаплулировал всю возможную логику во вселенной — смотрите часть статьи отношение Visitor и ООП

Вы сводите паттерн Посетитель к элементарному вызову функции f(o). Зачем вообще какой-то паттерн, чтобы передать объект в функцию? Не надо приводить типы, это необязательно (в супер классе обычно есть общие методы).

Я каюсь, что не штудировал перед ответом GoF, да и Котлин не особо знаю. Но использую этот паттерн в одном проекте. И Посетитель как раз позволяет избавиться от конструкций switch (и их аналогов) в клиентском коде, предоставляя возможность объекту самостоятельно выбрать нужный метод посетителя.

Получается: ответственность за функциональность на посетителе, а ответственность за выбор нужного метода — внутри объекта.

>> Не надо приводить типы, это необязательно (в супер классе обычно есть общие методы).


Шаблон Visitor не нужен там, где функциональность присутствует в суперклассе. Visitor подходит для функциональности, внешней по отношению к классу и, тем более, суперклассу. В случае метода суперкласса работает чистый полиморфизм ООП без всяких шаблонов.


Visitor избавляет от switch и аналогов — это его прелесть. Однако это имеет смысл, когда switch/when/if читаются хуже (или по производительности проседают). Например, в Java реализация Visitor имеет смысл — он просто красив по сравнению с альтернативой. То же и в С++. В данной статье я рассматриваю только Kotlin и только для него вывод — код без Visitor более читаемый (и производительный). Я не свожу Visitor к элементарному вызову функции — я предлагаю заменить его элементарным вызовом функции.

Эх… Без примера не разобраться. Мы немного о разном.

Вот скажите, как ваша итоговая реализация будет ввести себя для класса, который одновременно расширяет и Cat и Human?

Как будет работать эта реализация для класса SuperHuman: Human? Который днём ведёт себя как обычный человек, а ночью летает?

одновременно расширяет и Cat и Human?


Множественное наследование не поддерживается в Kotlin. Это если кратко. Если уйти далеко от изначальной постановки вопроса и начать играться с интерфейсами, то ответ зависит от порядка опций is в блоке when


Как будет работать эта реализация для класса SuperHuman: Human?
Если предусмотреть блок is SuperHuman ->, то он будет отрабатывать, если нет, то будет проваливаться в блок для Human. Ничего неожиданного, по-моему.

Короче, Посетитель нужен там, где instance of не справляется. Если по-простому

Я считаю, что Посетитель нужен, чтобы было интересней жить. Серьезно — я это в выводах написал. Очень интересный шаблон. Выше в комментариях указали, что он ещё может пригодится для предотвращения ошибок неполной реализации функционала.

Отличный примет visitor в некотором виде это парсинг (XML например). У вас есть интерфейс NodeVisitor и методы (visitComment, visitElement, visitAttribute ...).
Вы никак не можете написать instanceof, потому что библиотека парсинга XML ничего не знает о вашей бизнес логике!
P.S. Вообще-то, основное преимущество ООП и паттернов проектирования, это inversion of control, т.е. вы строите абстракцию и делайте универсальное решение, что позволяет упаковывать 100К кода в разумные модули. Конечно, если проект состоит из 500-1000 LOC, тут можно и в один класс все запихать.
Это уже не сможет скомпилироваться, так как нет метода visit(LivingCreature)


Я, наверное, из другого мира (С#), но почему-то вот такой пример компиляется на ура:

using System;
					
public class Program
{
	public class LivingCreature
	{}
	
	public class Human: LivingCreature
	{ 
		public string name;
	}
	
	public class Cat: LivingCreature
	{ 
		public string color;
	}
	
	interface Visitor {
		void visit( Human creature);
		void visit(Cat creature );
	}
	
	public class Guest: Visitor {
		public void visit( Human human){
			Console.WriteLine("Hello " + human.name);
		}
		public void visit(Cat cat ){
			Console.WriteLine("Pet " + cat.color);
		}
	}
	
	public static void Main()
	{
		var human = new Human() {name = "Jorge" };
		var cat = new Cat() {color = "pink" };
		
		
		
		var g = new Guest();
		g.visit(human);
		g.visit(cat);
	}
}


Поясните, плиз, почему у Вас не скомпиляется?

Ваш код идентичен первому листингу статьи. Не компилируется второй листинг, где переменные human и cat явно объявлены с типом суперкласса. Попробуйте что-то вроде этого :


LivingCreature human = new Human() {name = "Jorge" };
Да, спасибо за уточнение. Теперь проблема и в C# всплыла.
Sign up to leave a comment.

Articles