Originally I had posted the question on CodeReview, but this is more suited for StackOverflow probably.
I'm coding for a multi-step process, using Java 6. Say there are 3 of these steps.
Each accepts the same type of input.
Lets begin.
This is the object which is passed as input to each step. This object acts as a wrapper for another type of object, alongside some steps' shared values. Be aware names are translated to a more generic domain and in english, originals are in Italian.
public class EntityStepInput<T extends Entity> {
public final T entity;
public boolean modified;
public boolean canceled;
public EntityStepInput(final T entity) {
this.entity = entity;
}
}
This is the interface used by each step.
public interface EntityStep<T extends EntityStepInput<? extends Entity>> {
void process(final T stepInput) throws Exception;
}
Now, 2 out of the 3 steps must accept an EntityStepInput
which contains a Entity
or any type derived from it.
public class FirstEntityStep implements EntityStep<EntityStepInput<? extends Entity>> {
@Override
public void process(final EntityStepInput<? extends Entity> stepInput) throws Exception {}
}
public class SecondEntityStep implements EntityStep<EntityStepInput<? extends Entity>> {
@Override
public void process(final EntityStepInput<? extends Entity> stepInput) throws Exception {}
}
The last step must accept an EntityStepInput
which contains a specific type derived from Entity
.
public class ThirdEntityStep implements EntityStep<EntityStepInput<? extends DerivedEntity>> {
@Override
public void process(final EntityStepInput<? extends DerivedEntity> stepInput) throws Exception {}
}
The usage is pretty straighforward. I have overloaded methods which accept different types of Entity
s. What follows is a simplified version.
public void workWithEntity(final DerivedEntity entity) throws Exception {
final EntityStepInput<DerivedEntity> stepInput = new EntityStepInput<DerivedEntity>(entity);
stepOne.process(stepInput);
stepTwo.process(stepInput);
stepThree.process(stepInput);
}
As you can see the DerivedEntity
type is able to use all of the steps.
public void workWithEntity(final OtherDerivedEntity entity) throws Exception {
final EntityStepInput<OtherDerivedEntity> stepInput = new EntityStepInput<OtherDerivedEntity>(entity);
stepOne.process(stepInput);
stepTwo.process(stepInput);
}
And here another type of Entity
cannot use the last step, which is what I want.
Now, this has became quite complex with generics. I fear who will read my code after I'm gone won't understand and sooner or later a mess is going to be made.
Is this simplifiable? What would your approach be like to respect as much as possibile the single responsibility principle?
Edit. Entity
hierarchy is as follow:
Entity > DerivedEntity
Entity > OtherDerivedEntity
A generic type or generic class is a complex type that can be combined with other different types to produce a new type. The most common application of generic types is for “container” classes like lists, stacks, and queues.
Code that uses generics has many benefits over non-generic code: Stronger type checks at compile time. A Java compiler applies strong type checking to generic code and issues errors if the code violates type safety. Fixing compile-time errors is easier than fixing runtime errors, which can be difficult to find.
Limitation of generics Firstly, primitive types (like int , long , byte , …) are not allowed to be used in generics. It means whenever you need to parameterize your generic type with a primitive one, the respective class wrapper ( Integer , Long , Byte , …) has to be used instead.
Here's my second attempt at an answer. I think the system that you're using currently looks good; it provides compile-time checking, as you don't want to allow Step #3 to attempt to process any type which is not a DerivedEntity
(or one of its children).
You can simplify this by using a List<Consumer<? extends Entity>>
, but you lose compile-time type checking for Step #3 and are forced to ameliorate it by using instanceof
:
Java 8+ solution:
List<Consumer<Entity>> processes = new ArrayList<>();
processes.add(entity -> {
// Process first step.
});
processes.add(entity -> {
// Process second step.
});
processes.add(entity -> {
if (!(entity instanceof DerivedEntity)) {
System.out.println("Step 3: The entity must be a DerivedEntity!");
return;
}
// Process third step.
});
To pass an Entity
through the pipeline, it's as simple as:
processes.forEach(consumer -> consumer.accept(entity));
Java 6+ solution (let's create our own Consumer
interface!):
public interface Consumer<T> {
void accept(T t);
}
The same code as above, but using our Consumer
interface:
List<Consumer<Entity>> processes = new ArrayList<Consumer<Entity>>();
processes.add(new Consumer<Entity>() {
@Override
public void accept(Entity entity) {
// Process first step.
}
});
processes.add(new Consumer<Entity>() {
@Override
public void accept(Entity entity) {
// Process second step.
}
});
processes.add(new Consumer<Entity>() {
@Override
public void accept(Entity entity) {
if (!(entity instanceof DerivedEntity)) {
System.out.println("Step 3: The entity must be a DerivedEntity!");
return;
}
// Process third step.
}
});
Entity entity = new DerivedEntity();
for (Consumer<Entity> consumer : processes) {
consumer.accept(entity);
}
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