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!
Explanation. Behavioral design patterns are specifically concerned with communication between objects.
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.
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>
.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With