Comments 17
А именно: в случае добавления/удаления классов все контракты просто перестают компилироваться в отличие от instanceof-проверок.
Всегда рассматривал это как побочный эффект. Но, пожалуй, тоже можно записать в плюс шаблона.
Если же использовать sealed class в when реализации, как приведено тут, то тоже будет валиться с ошибкой компиляции, как и в классическом Visitor. Конечно, не всегда sealed удобен.
Почему-то сам автор не подчеркнул этого.
interface Visitor {
fun visitHuman(creature: LivingCreature)
fun visitAnimal(creature: LivingCreature)
}
то стало бы понятно, что преимущество шаблона посетитель в том, что вы не зависите от конкретных классов. И экземпляр Human, если ему вдруг захочется, может сам решить — мяукать ему или разговаривать))
в этом случае нет проверки на тип аргумента и возможна ошибка передачи типа visitHuman( Cat() )
А даже без ошибки программиста пришлось бы приводить тип аргумента, что является плохой практикой и классичиский Visitor ее устраняет.
Экземпляр Human может решать, мяукать или разговаривать, но речь идет о функционале вне Human, потому что с помощью Visitor мы реализовываем то, что не относится к обязанностям объекта. Нам нужны Visitor для того, чтобы оригинальный объект не инкаплулировал всю возможную логику во вселенной — смотрите часть статьи отношение Visitor и ООП
Я каюсь, что не штудировал перед ответом 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. Ничего неожиданного, по-моему.
Вы никак не можете написать 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);
}
}
Поясните, плиз, почему у Вас не скомпиляется?
Шаблон Visitor устарел для Kotlin, но знать его стоит