Подтвердить что ты не робот

Как шаблон посетителя не нарушает принцип открытого закрытия?

Материал из Википедии:

Идея заключалась в том, что после завершения реализация класса может быть изменена только до правильные ошибки; новые или измененные функции потребуют создания другого класса. Этот класс может повторно использовать кодирование из исходного класса через наследование

Из того, что я понимаю, шаблон "Посетитель" - это мощная техника для прохождения аналогичных, но разных объектов, реализующих один и тот же интерфейс с помощью двойной отправки. В одном из моих примеров Java я создал составной набор объектов, которые образуют древовидную структуру, и каждая конкретная реализация этих объектов реализует интерфейс, доступный для посещения. Интерфейс посетителя имеет метод для каждого из посещаемых объектов, а конкретный посетитель реализует, что делать для каждого из этих случаев.

То, что я пытаюсь понять, это тот факт, что если бы я добавил новую реализацию в составную структуру, которая также реализует доступность посещения, то мне нужно снова открыть интерфейс посетителя и добавить к нему этот случай, также заставляя меня модифицировать каждую реализацию посетителя.

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

4b9b3361

Ответ 1

Образец применим к некоторым случаям. Из книги GoF (стр. 333):

Используйте шаблон посетителя, если

  • [...]

  • классы, определяющие структуру объекта, редко меняются, но вы часто хотите определить новые операции над структурой. Изменение классы объектов требуют переопределения интерфейса для всех посетителей, что потенциально дорого. Если классы структуры объектов часто меняются, то, вероятно, лучше определить операции в эти классы.

Если вы часто меняете классы объектов, входящих в структуру, иерархию классов посетителей можно с трудом поддерживать. В таком случае может быть проще определить операции над классами, которые составляют структуру.

Ответ 2

Джон Влиссидес, один из ГоФ, написал прекрасную главу по этому вопросу в своей Patterns Hatching. Он обсуждает тот факт, что расширение иерархии несовместимо с сохранением посетителя. Его решение представляет собой гибрид между посетителем и подходом enum (или основанным на типе), где посетителю предоставляется метод visitOther, который вызывается всеми классами за пределами "базовой" иерархии, которые пользователь понимает из коробки. Этот метод предоставляет вам возможность избежать классов, добавленных в иерархию после того, как посетитель был доработан.

abstract class Visitable {
    void accept(Visitor v);
}
class VisitableSubclassA extends Visitable  {
    void accept(Visitor v) {
        v.visitA(this);
    }
}
class VisitableSubclassB extends Visitable {
    void accept(Visitor v) {
        v.visitB(this);
    }
}
interface Visitor {
    // The "boilerplate" visitor
    void visitB(VisitableSubclassA a);
    void visitB(VisitableSubclassB b);
    // The "escape clause" for all other types
    void visitOther(Visitable other);
}

При добавлении этой модификации ваш посетитель больше не нарушает принцип Open-Close, потому что он открыт для расширения без необходимости изменять его исходный код.

Я пробовал этот гибридный метод по нескольким проектам, и он работал достаточно хорошо. Моя основная иерархия классов определяется в отдельно скомпилированной библиотеке, которая не нуждается в изменении. Когда я добавляю новые реализации Visitable, я изменяю реализацию Visitor, чтобы ожидать этих новых классов в своих методах visitOther. Поскольку посетители и расширяющиеся классы находятся в одной и той же библиотеке, этот подход работает очень хорошо.

P.S. Существует еще одна статья под названием "Пересмотренный посетитель" , в которой обсуждается именно этот вопрос. Автор приходит к выводу, что можно вернуться к двойной отправке на enum, потому что исходный шаблон посетителя не оказывает существенного улучшения по сравнению с отправкой enum. Я не согласен с автором, потому что в тех случаях, когда основная часть вашей иерархии наследования прочная, и пользователи должны предоставлять несколько реализаций здесь и там, гибридный подход обеспечивает значительные преимущества в удобочитаемости; нет смысла бросать все из-за пары классов, с которыми мы можем с легкостью вписаться в иерархию.