Существует ли шаблон для различного количества входов / выходов, но с той же основной ответственностью за работу? - PullRequest
5 голосов
/ 17 апреля 2020

В моей кодовой базе есть шаблон, который выглядит примерно так: мы обрабатываем сообщения из очереди и затем передаем это сообщение в следующую очередь. До сих пор использовался тот случай, когда мы обрабатываем и создаем сообщения одного типа.

public interface Processor<T> {
  T process(T thing);
}

Этот вариант использования перешел к обработке и производству другого типа. Кроме того, нам может понадобиться обработать один тип и создать ряд других.

Так что-то вроде:

public interface NewProcessor<I, O> {
  O process(I thing;)
}

и в будущем, вероятно, потребуется что-то вроде

public interface FutureProcessor<I, O1, O2> { //potentially N number of O
  Pair<O1, O2> process(I thing);
}

Мой вопрос: есть ли способ express этот очиститель, чем три отдельных класса? Есть ли здесь хорошая известная иерархия, которую я мог бы использовать?

У нас есть абстрактный пользователь процессора первого типа, который я предпочел бы не переписывать каждый раз, когда мы добавляем новый процессор. Сегодня он делает что-то вроде этого:

public abstract AbstractModule<T> {
  private Processor<T> processor;
  public AbstractModule(Processor<T> processor) {
   this.processor = processor;
  }

  T runModule(T input) {
    // abstract validateInput(input);
    T result = processor.process();
    // record results
    return result;
  }
}

Будем благодарны за любые известные шаблоны или предложения о том, как это сделать!

Ответы [ 5 ]

4 голосов
/ 24 апреля 2020

Ваш вариант использования - получить объект, что-то с ним сделать и создать новый. В основном функция одного аргумента. Я думаю, что критической частью является количество аргументов, потому что вы можете вернуть только один результат в Java. Если вы уверены, что вам не понадобится обрабатывать более одного объекта за раз в будущем, то нет более универсального c способа сделать это, используя функцию, подобную той, которую вы уже определили (я бы использовал JDK, когда это возможно, хотя, в этом случае Function и UnaryOperator).

Допустим, вы хотите продолжать использовать свои пользовательские функции, и вы не Я не хочу менять свой AbstractModule, как вы сказали. Я бы переименовал Processor как UnaryProcessor по крайней мере. Тогда я изменил бы его следующим образом:

public interface Processor<T, R> {
    R process(T t);
}

public interface UnaryProcessor<T> extends Processor<T, T> {}

На этом этапе вы сможете обработать последний вариант использования, который вы упомянули как Processor<T, Pair<O1, O2>>. Тот же лог c будет применяться к любому будущему варианту использования, вам просто нужно заменить тип возвращаемого значения любым типом, который вам нужен в данный момент, например List<E>.

1 голос
/ 20 апреля 2020

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

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

interface Processor<T> {
  void process(T message);
}

interface Output<T> {
  result(T result);
}

class SomeKindOfProcessor implements Processor<SomeInput> {

  private final Output<? super Foo> foo;
  private final Output<? super Bar> bar;

  // These parameters are probably injected by some sort of IoC container.
  // They are easy to mock in tests too, so you can unit test this
  // class in isolation.
  SomeKindOfProcessor(Output<? super Foo> foo, Output<? super Bar> bar) {
    this.foo = foo;
    this.bar = bar;
  }

  @Override
  public void process(SomeInput input) {
     /* Do some work that results in a Bar instance */
     bar.result(new Bar(...));
     /* Do some more work that might result in a Foo instance. */
     if (...) {
        foo.result(new Foo(...));
     }
  }

}

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

0 голосов
/ 25 апреля 2020

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

class Decorator<I, O, D> implements Function<I, D>
{
    private final Function<I, O> function;
    private final Function<O, D> delegate;

    public Decorator( Function<I , O> function, Function<O, D> delegate )
    {
        this.function = function;
        this.delegate = delegate;
    }

    @Override
    public D apply( I input )
    {
        O output = function.apply( input );
        return delegate.apply( output );
    }
}

Вот простой пример преобразования строк в целые числа, изменения строки и затем преобразование обратно в целое число:

@Test
public void testDelegate()
{
    Function<Integer, String> intToString = String::valueOf;

    Decorator<Integer, String, String> repeatString = new Decorator<>(
            intToString, s -> s + s );

    assertEquals( "33", repeatString.apply( 3 ));

    Decorator<Integer, String, Integer> repeatThenToInt = new Decorator<>(
            repeatString, Integer::valueOf );

    assertEquals( 33, repeatThenToInt.apply( 3 ).intValue() );
}
0 голосов
/ 24 апреля 2020

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

Вы должны предпочесть дать агрегату полезное имя, для пары целых можно назвать, например, Vertex, Point, Dimensions et c. в зависимости от того, что представляет эти целые. Только если нет полезного имени для вашего агрегата, и вы не собираетесь использовать его в другом месте, рекомендуется использовать обобщенный c, такой как ProcessInput или ProcessOutput.

Пример aggreget:

class Vertex {
   final public int x;
   final public int y;

   public Vertex(int x, int y) {
      this.x = x;
      this.y = y;
   }
}

Я обычно предпочитаю сохранять поля в таком итоговом итоговом значении, чтобы сделать время неизменным, но нет ничего, что требовало бы от вас этого.

Это похоже на то, как некоторые другие предлагали создать универсальный шаблон. c совокупность. Не думай, что тебе нужно это делать. Я обычно предпочитаю агрегаты с богом полезными именами, где отдельные поля или свойства могут также иметь хорошие полезные имена. Необходимо помнить, что третье целое число - это число или вторая строка - это имя, что может привести к тому, что код будет не так легко прочитать.

0 голосов
/ 17 апреля 2020

Ваш результат может быть абстрактным, и вы всегда его возвращаете

class Result<T1, T2, T3>{// only change Result class   
    boolean hasType1     //no need to change method interface that returns Result
    boolean hasType2
    boolean hasType3

    T1 t1
    T2 t2
    T3 t3    
    // you can use other implementation such as array...
}

public interface Processor<T> {//only one interface that always returns Result
  Result process(T thing);
}
...