Реализация шаблона посетителя в случае отсутствия исходного кода

одна из причин, чтобы рассмотреть Visitor_pattern:

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

предположим, что у вас нет исходного кода сторонних библиотек и вы добавили одну операцию над связанными объектами.

поскольку у вас нет объекта, ваши элементы (сторонние классы) не могут быть изменены добавить посетителя.

enter image description here

В этом случае, двойная отправка невозможна.

Так какой вариант обычно предпочтительнее?

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.

недостатки:

  1. довольно много шаблонных. Использовать Ломбок если вы разрабатываете на Java.
  2. довольно много экземпляров объектов-оболочек. Может быть или не быть проблемой для вас.
  3. вы должны знать конкретный тип объектов заранее. Это означает, что вы знаете их подтип, они не являются пакетами в списке. Если это так, у вас нет другого выбора, кроме как использовать отображение.

должна быть возможность добавления новых функциональных возможностей в классы некоторой иерархии, без изменения интерфейса базового класса. Виды возможного поведения должны быть постоянными, в то время как операции для разных классов должны выполняться по-разному.

на Шаблон 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);

и он напечатает вам "я-библиотека";