Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a pattern for having a differing number of Input/Output, but the same core responsibility of work?

There is a pattern in my codebase that looks a lot like this: We are processing messages from a queue and then passing that message along to the next queue. The use case up until now has been that we process and produce the same type of message.

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

That use case has evolved to processing and producing a different type. In addition, we may need to process one type and produce a series of others.

So something like:

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

and in the future will likely need something like

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

My question is: Is there a way to express this cleaner than having three separate classes? Is there a nice known hierarchy I could use here?

We have an abstract user of the first type of processor that I would prefer to not have to re-write every time we add a new processor. It does something like this today:

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;
  }
}

Any known patterns or suggestions on how to do this would be appreciated!

like image 279
Anthony Avatar asked Apr 17 '20 01:04

Anthony


People also ask

Which type of design patterns are specifically concerned with communication between objects?

Explanation. Behavioral design patterns are specifically concerned with communication between objects.

What is structural design pattern?

In software engineering, structural design patterns are design patterns that ease the design by identifying a simple way to realize relationships among entities. Examples of Structural Patterns include: Adapter pattern: 'adapts' one interface for a class into one that a client expects.


Video Answer


2 Answers

Your use case is getting an object, doing something with it and generating a new one. Basically a function of one argument. The critical part I think is the amount of arguments because you can only return one result in Java. If you are sure you won't need to process more than one object at a time in the future then there is no more generic way to do that that using a function like the one you have already defined (I would use the JDK ones when possible though, in this case Function and UnaryOperator).

Let's say you want to keep using your custom functions and you don't want to change your AbstractModule as you said. I would rename Processor as UnaryProcessor at least. Then I would change it as follows:

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

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

At this point you will be able to handle the last use case you mentioned as Processor<T, Pair<O1, O2>>. The same logic would apply to any future use case, you just need to replace the return type by any type you need at that moment, for example a List<E>.

like image 74
acm Avatar answered Sep 29 '22 07:09

acm


Instead of returning results from the method call, initialize each processor with callbacks for each result it may generate from an input.

This is well-suited for a queue-processing application, since the callback might produce a new message for a different queue, and you can intercept the result easily to do things like monitoring without modifying the processor.

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(...));
     }
  }

}

If the callback implementation needs to correlate results with the input, you can generally use some identifier from the input to do that.

like image 43
erickson Avatar answered Sep 29 '22 08:09

erickson