Реализация шаблона посетителя в случае отсутствия исходного кода
одна из причин, чтобы рассмотреть Visitor_pattern:
практическим результатом этого разделения является возможность добавления новых операций в существующие структуры объекта без изменения этих структур.
предположим, что у вас нет исходного кода сторонних библиотек и вы добавили одну операцию над связанными объектами.
поскольку у вас нет объекта, ваши элементы (сторонние классы) не могут быть изменены добавить посетителя.
В этом случае, двойная отправка невозможна.
Так какой вариант обычно предпочтительнее?
Option 1:
расширить еще одну иерархию наследования поверх класса третьей стороны и реализовать шаблон, как показано на рисунке с двойной отправкой?
для данной иерархии класса B, которая расширяет класс A, я добавлю
ElementA extends A
ElementB extends B
Теперь ConcreteElements являются производный от ElementA вместо класса A.
минусы: количество классов будет расти.
Option 2:
используйте класс посетителя a центральный вспомогательный класс и выполняйте работу с одной отправкой.
минусы: мы на самом деле не следуем за посетителем в соответствии с диаграммой UML.
поправьте, если я ошибаюсь.
6 ответов
вы могли бы объединить фантик и гость для решения ваших проблем.
Используя обертке добавить a visit
способ позволяет повысить удобство использования этих объектов. Конечно, вы получаете все преимущества (меньше зависимости от устаревших классов) и недостатки (дополнительные объекты) оболочки.
вот проработанный пример на JAVA (потому что он довольно строгий, не делает двойной отправки сам по себе, и Я с ним хорошо знаком):
1) ваши устаревшие объекты
предполагая, что у вас есть ваши устаревшие объекты Legacy1
и Legacy2
что ты не может изменить, которые имеют конкретные методы ведения бизнеса:
public final class Legacy1 {
public void someBusinessMethod1(){
...
}
}
и
public final class Legacy2 {
public void anotherBusinessMethod(){
...
}
}
2) Подготовьте обертку
вы просто завернуть их в VisitableWrapper С visit
метод, который принимает ваш гость, например:
public interface VisitableWrapper {
public void accept(Visitor visitor);
}
со следующими реализациями:
public class Legacy1Wrapper {
private final Legacy1 legacyObj;
public Legacy2Wrapper(Legacy1 original){
this.legacyObj = original;
}
public void accept(Visitor visitor){
visitor.visit(legacyObj);
}
}
и
public class Legacy2Wrapper {
private final Legacy2 legacyObj;
public Legacy2Wrapper(Legacy2 original){
this.legacyObj = original;
}
public void accept(Visitor visitor){
visitor.visit(legacyObj);
}
}
3) посетитель, наготове!
тогда ваш собственный гостьs можно установить для посещения оболочки так:
public interface Visitor {
public void visit(Legacy1 leg);
public void visit(Legacy2 leg);
}
С реализацией, например, так:
public class SomeLegacyVisitor{
public void visit(Legacy1 leg){
System.out.println("This is a Legacy1! let's do something with it!");
leg.someBusinessMethod1();
}
public void visit(Legacy2 leg){
System.out.println("Hum, this is a Legacy 2 object. Well, let's do something else.");
leg.anotherBusinessMethod();
}
}
4) раскрыть силу
наконец, в вашем коде эта структура будет работать следующим образом:
public class TestClass{
// Start off with some legacy objects
Legacy1 leg1 = ...
Legacy2 leg2 = ...
// Wrap all your legacy objects into a List:
List<VisitableWrapper> visitableLegacys = new ArrayList<>();
visitableLegacys.add(new Legacy1Wrapper(legacy1));
visitableLegacys.add(new Legacy2Wrapper(legacy2));
Visitor visitor = new SomeLegacyVisitor(); // Use any of your visitor implementation !
for(VisitableWrapper wrappedLegacy: visitableLegacys){
wrappedLegacy.accept(visitor);
}
}
ожидаемый вывод:
This is a Legacy1! let's do something with it!
Hum, this is a Legacy 2 object. Well, let's do something else.
недостатки:
- довольно много шаблонных. Использовать Ломбок если вы разрабатываете на Java.
- довольно много экземпляров объектов-оболочек. Может быть или не быть проблемой для вас.
- вы должны знать конкретный тип объектов заранее. Это означает, что вы знаете их подтип, они не являются пакетами в списке. Если это так, у вас нет другого выбора, кроме как использовать отображение.
должна быть возможность добавления новых функциональных возможностей в классы некоторой иерархии, без изменения интерфейса базового класса. Виды возможного поведения должны быть постоянными, в то время как операции для разных классов должны выполняться по-разному.
на Шаблон Visitor позволяет сконцентрировать все операции в одном классе. Там может быть много Concrete Element
классы (из диаграммы), но для каждого из них будет реализовано visit()
метод Concrete Visitor
класс, который определит свой собственный алгоритм.
определение и реализация метода для каждого подкласса Element
класс:
public interface Visitor {
void visit(Element element);
}
public class ConcreteVisitor implements Visitor {
public void visit(Element element) {
// implementation
}
}
на Шаблон Visitor легко расширяется для новых операций путем реализации этого интерфейса новым классом с его реализацией метода.
следующая структура инкапсулирует Element
класс:
public lass ObjectStructure {
private Element element;
// some methods
}
этой ObjectStructure
класс может агрегировать один или несколько экземпляров Element
. Презентации Visitor
действует на:
public interface Element {
void accept(Visitor visitor);
}
и реализации accept()
метод в конкретной сущности:
public class ConcreteElement implements Element {
public void accept(Visitor visitor) {
visitor.visit();
}
}
использование Шаблон Visitor позволяет сохранить Element
иерархия из огромной логической функциональности или сложной конфигурации.
желательно добавить функциональность ко всем классам иерархии при определении нового Visitor
подклассы. Но может возникнуть проблема:--5--> должны быть переопределенным для каждого типа иерархии. Чтобы избежать этого лучше определить AbstractVisitor
класс и все оставляют его всем visit()
способ тела пустые.
вывод: использование этого шаблона хорошо, когда иерархия классов типа Element
остается неизменным. Если добавляются новые классы, это обычно приводит к значительным изменениям в классах Visitor
тип.
мой ответ очень похож на Михаэля фон Венкштерна, с улучшениями, которые у нас есть по имени accept
метод (больше похож на стандартный шаблон) и что мы обрабатываем неизвестные конкретные классы-нет никакой гарантии, что в какой-то момент конкретная реализация, которую мы не видели раньше, не появится на пути к классам.
Мой посетитель также позволяет возвращаемое значение.
Я также использовал более подробное имя visit
методы, включая тип в имя метода, но это не необходимо, вы можете назвать их все visit
.
// these classes cannot be modified and do not have source available
class Legacy {
}
class Legacy1 extends Legacy {
}
class Legacy2 extends Legacy {
}
// this is the implementation of your visitor
abstract class LegacyVisitor<T> {
abstract T visitLegacy1(Legacy1 l);
abstract T visitLegacy2(Legacy2 l);
T accept(Legacy l) {
if (l instanceof Legacy1) {
return visitLegacy1((Legacy1)l);
} else if (l instanceof Legacy2) {
return visitLegacy2((Legacy2)l);
} else {
throw new RuntimeException("Unknown concrete Legacy subclass:" + l.getClass());
}
}
}
public class Test {
public static void main(String[] args) {
String s = new LegacyVisitor<String>() {
@Override
String visitLegacy1(Legacy1 l) {
return "It's a 1";
}
@Override
String visitLegacy2(Legacy2 l) {
return "It's a 2";
}
}.accept(new Legacy1());
System.out.println(s);
}
}
сначала я должен был сделать несколько предположений о устаревшем коде, так как вы не предоставили много подробностей об этом. Допустим, мне нужно добавить новый метод Legacy
без переопределения все. Вот как я это сделаю:--7-->
public interface LegacyInterface {
void A();
}
public final class LegacyClass implements LegacyInterface {
@Override
public void A() {
System.out.println("Hello from A");
}
}
сначала продлевает "контракт"
public interface MyInterface extends LegacyInterface {
void B();
}
и реализовать его в "оформлен" путем
public final class MyClass implements MyInterface {
private final LegacyInterface origin;
public MyClass(LegacyInterface origin) {
this.origin = origin;
}
@Override
public void A() {
origin.A();
}
@Override
public void B() {
System.out.println("Hello from B");
}
}
ключевым моментом является MyInterface extends LegacyInterface
: это гарантия реализации выиграют от как службы из устаревшего кода, так и ваши личные добавления.
использование
MyInterface b = new MyClass(new LegacyClass());
Я думаю, что лучший подход-это Option 1:
расширьте еще одну иерархию наследования поверх класса третьей стороны и реализуйте шаблон посетителя с двойной отправкой.
проблема заключается в количестве дополнительных классов, которые вам нужны, но это можно решить с помощью динамического декоратора обертки. Декоратор обертки-это способ добавить реализацию интерфейса, методы и свойства к уже существующим obejcts: как реализовать декоратор обертки в Ява?
таким образом, вам нужен ваш Visitor
интерфейс и поставить там visit(L legacy)
методы:
public interface Visitor<L> {
public void visit(L legacy);
}
в AcceptInterceptor вы можете поместить код для метода accept
public class AcceptInterceptor {
@RuntimeType
public static Object intercept(@This WrappedAcceptor proxy, @Argument(0) Visitor visitor) throws Exception {
visitor.visit(proxy);
}
}
The WrappedAcceptor
интерфейс определяет метод, чтобы принять посетителя и установить и получить завернутый объект
interface WrappedAcceptor<V> {
Object getWrapped();
void setWrapped(Object wrapped);
void accept(V visitor);
}
и, наконец, код утилиты для создания обертки вокруг любого obect:
Class<? extends Object> proxyType = new ByteBuddy()
.subclass(legacyObject.getClass(), ConstructorStrategy.Default.IMITATE_SUPER_TYPE_PUBLIC)
.method(anyOf(WrappedAcceptor.class.getMethods())).intercept(MethodDelegation.to(AcceptInterceptor.class))
.defineField("wrapped", Object.class, Visibility.PRIVATE)
.implement(WrappedAcceptor.class).intercept(FieldAccessor.ofBeanProperty())
.make()
.load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
.getLoaded();
WrappedAcceptor wrapper = (WrappedAcceptor) proxyType.newInstance();
wrapper.setWrapped(legacyObject);
если ваша библиотека не имеет accept
методы, которые вам нужно сделать с instanceof
. (Обычно вы дважды выполняете одинарную диспетчеризацию на Java для эмуляции двойной диспетчеризации; но здесь мы используем instanceof
для эмуляции двойной диспетчеризации).
вот пример:
interface Library {
public void get1();
public void get2();
}
public class Library1 implements Library {
public void get1() { ... }
public void get2() { ... }
}
public class Library2 implements Library {
public void get1() { ... }
public void get2() { ... }
}
interface Visitor {
default void visit(Library1 l1) {}
default void visit(Library2 l2) {}
default void visit(Library l) {
// add here instanceof for double dispatching
if (l instanceof Library1) {
visit((Library1) l);
}
else if (l instanceof Library2) {
visit((Library2) l);
}
}
}
// add extra print methods to the library
public class PrinterVisitor implements Visitor {
void visit(Library1 l1) {
System.out.println("I am library1");
}
void visit(Library2 l2) {
System.out.println("I am library2");
}
}
и теперь в любом методе можно написать:
Library l = new Library1();
PrinterVisitor pv = new PrinterVisitor();
pv.visit(l);
и он напечатает вам "я-библиотека";