Как я могу заменить внедрение конструктора в декораторы внедрением метода, чтобы получить цепочку и заставить существующий код работать как есть?

#java

#java

Вопрос:

У нас есть код, который выглядит следующим образом:

 Service<E,F> pipeline = new MyServiceDecorator2(new MyServiceDecorator1(new MyService()));
  

Затем это выполняется как F f = pipeline.apply(new E("E"))

Мы хотели бы, чтобы это выглядело примерно так:

 Service<A,B> myService = new MyService();
// Service<C,D>
MyServiceDecorator1 myServiceDecorator1 = new MyServiceDecorator1();
// Service<E,F>
MyServiceDecorator2 myServiceDecorator2 = new MyServiceDecorator2();

Service<E,F> pipeline = myServiceDecorator2.andThen(myServiceDecorator1).andThen(myService);

// This should still be doable i.e., the end goal
F f = pipeline.apply(new E("E"));
  

Мы пробовали различные приемы, но мы не можем заставить возвращаемые типы выстраиваться правильно. Вышеописанное привело бы к ошибке — мы просто добавили andThen к каждому классу декоратора вручную, просто чтобы понять поток, вот так:

 public <J,K> Service andThen(Service<J,K> next) {
 this.service = next;
 return next;
}
  

Это возвращает тип «следующего элемента» в цепочке. Я попробовал несколько трюков со next/prev ссылками / указателями, чтобы подняться по цепочке, но, похоже, ничего не работает. Возможно ли это вообще?

Вот REPL, показывающий код с инструкцией print, показывающей прогресс через декораторы.

Контекст: У нас есть достаточное количество кода, который можно упростить до «декораторов» для реализации шаблона «канал и фильтр», чтобы мы могли предоставить базовую «структуру», позволяющую разработчикам применять тот же шаблон / мышление для решения той же проблемы, а не копировать / макетировать или изобретать велосипед. Приведенное выше является «примером» того, чего мы намерены достичь. Мы планируем преобразовать это в generics, но пока есть дублирующий код.

Комментарии:

1. Смотрим на ваш пример (REPL) Я вижу, что оформленные сервисы «исправлены». Это то, чего вы хотите? Декоратор может украсить определенный сервис. Правильно?

2. Может ли apply принимать домен, а не определенный тип?

Ответ №1:

Редактировать: работаю над другим подходом (я думаю, что у него та же проблема …)

Ну, вы действительно не можете получить то, что хотите, вам нужно найти компромисс с чем-то.
В данном случае это относится к apply методу, который должен принимать Domain .

Это происходит потому, что обернутый Service код не задан во время построения, и поэтому он не может быть напечатан на 100%.

 interface Service<A extends Domain, B extends Domain> {
    B apply(final Domain a);
    Service<A, B> andThen(final Service<? extends Domain, ? extends Domain> service);
}
  

 class MyService implements Service<A, B> {
    private Service<? extends Domain, ? extends Domain> wrapped;

    @Override
    public B apply(final Domain a) {
        return new B(a.name   "->B");
    }

    @Override
    public Service<A, B> andThen(final Service<? extends Domain, ? extends Domain> wrapped) {
        this.wrapped = wrapped;
        return this;
    }
}
  

 class MyServiceDecorator1 implements Service<C, D> {
    private Service<? extends Domain, ? extends Domain> wrapped;

    @Override
    public D apply(final Domain input) {
        // C->A
        Domain a = new A(input.name   "->A");
        // get B
        Domain b = this.wrapped.apply(a);
        // B->D
        return new D(b.name   "->D");
    }

    public Service<C, D> andThen(final Service<? extends Domain, ? extends Domain> wrapped) {
        this.wrapped = wrapped;
        return this;
    }
}
  

 class MyServiceDecorator2 implements Service<E, F> {
    private Service<? extends Domain, ? extends Domain> wrapped;

    @Override
    public F apply(final Domain input) {
        // E->C
        Domain c = new C(input.name   "->C");
        // get D
        Domain d = this.wrapped.apply(c);
        // D->F
        return new F(d.name   "->F");
    }

    @Override
    public Service<E, F> andThen(final Service<? extends Domain, ? extends Domain> wrapped) {
        this.wrapped = wrapped;
        return this;
    }
}
  

 public static void main(String[] args) {
    final Service<A, B> myService = new MyService();
    MyServiceDecorator1 myServiceDecorator1 = new MyServiceDecorator1();
    MyServiceDecorator2 myServiceDecorator2 = new MyServiceDecorator2();

    final Service<E, F> efService =
            myServiceDecorator2.andThen(myServiceDecorator1)
                               .andThen(myService);

    // This should still be doable i.e., the end goal
    F f = efService.apply(new E("E"));
    System.out.println(f.name);
}
  

Я не мог бы сделать ничего лучше, поскольку Java ограничена возможностями дженериков.


Заключительные слова: генерация байт-кода — ваш друг.

Комментарии:

1. Это интересно и полезно. Искренне ценю усилия. Компромисс не так уж плох, хотя и мои Java Generics не самые лучшие, особенно после долгого перерыва!

2. @PhD Я думаю, что мой другой подход является тупиковым. Сейчас я слишком устал, но завтра попробую сделать это получше. Вопрос, каждый декоратор принимает определенный тип сервиса, верно? Не универсальный, потому что тогда было бы невозможно поддерживать безопасность типов достойным образом.

3. Это правильно. Сервис всегда имеет тип Service интерфейса, подобный тому, который вы опубликовали выше — не беспокойтесь об этом слишком сильно. Отдыхайте 🙂 На «конкретном уровне» каждый декоратор принимает известный тип. Идея состоит в том, чтобы каким-то образом параметризовать это с помощью дженериков / без них, чтобы сделать его повторно используемым — но если это невыполнимо, возможно, потребуется вернуться к чертежной доске

4. @PhD спасибо. Это интересная проблема, поэтому я попробую еще раз 😉