I am trying to create a builder which would help me to construct an object graph implemented using decorator pattern. I want the builder to be as reliable as possible, but it fails when generics are introduced with type bounds. That is it constructs an object graph which will fail when decorator "workflow" is called with "Cannot cast X to Y" error.
Builder usage:
Processor<XyzContext> processor = Builder.create(new XyzProcessor())
.persist()
.handleRequest()
.handleError()
.build();
Builder class used to decorate the original processor:
class Builder<T> {
Processor<T> processor;
Builder(Processor<T> processor) {
this.processor = processor;
}
static <T> Builder<T> create(Processor<T> processor) {
return new Builder<T>(processor);
}
Builder<T> handleRequest() {
processor = new RequestHandlingProcessor(processor);
return this;
}
public Builder<T> processNonBarcode() {
processor = new PersistingProcessor(processor);
return this;
}
public Builder<T> handleError() {
processor = new ErrorHandlingProcessor(processor);
return this;
}
public Processor<T> build() {
return processor;
}
}
Persisting processor (being decorated)
public class PersistingProcessor<T> implements Processor<T> {
private final Processor<T> delegate;
public PersistingProcessor(Processor<T> delegate) {
this.delegate = delegate;
}
@Override
public void process(T request) {
// save to db
}
}
Request handling decorator
public class RequestHandlingProcessor<T extends RequestEntityHolder & RawRequestHolder>
implements Processor<T> {
private final Processor<T> delegate;
public RequestHandlingProcessor(Processor<T> delegate) {
this.delegate = delegate;
}
@Override
public void process(T context) {
RequestEntity requestEntity = new RequestEntity();
requestEntity.setRaw(context.getRawRequest()); <------- RawRequestHolder interface used
context.setRequestEntity(requestEntity); <------------- RequestEntityHolder interface used
try {
delegate.process(context);
} catch (RuntimeException e) {
requestEntity.setError(e.getMessage());
throw e;
}
}
}
Error handling decorator
public class ErrorHandlingProcessor<T> implements Processor<T> {
private final Processor<T> delegate;
public ErrorHandlingProcessor(Processor<T> delegate) {
this.delegate = delegate;
}
@Override
public void process(T request) {
try {
delegate.process(request);
} catch (RuntimeException e) {
// do stuff
throw e;
}
}
}
The context class
public class XyzContext implements RequestEntityHolder, RawRequestHolder {
private String request;
private RequestEntity requestEntity;
public XyzContext(String request) {
this.request = request;
}
@Override
public String getRawRequest() {
return request.toString();
}
@Override
public RequestEntity getRequestEntity() {
return requestEntity;
}
@Override
public void setRequestEntity(RequestEntity requestEntity) {
this.requestEntity = requestEntity;
}
}
When I would use this builder for a different Context class, e.g. AbcContext class which does not implement RequestResponseHolder or RawRequestHolder, I would know about this unbound type error too late (when the decorated processor would be used for the first time to process something by throwing ClassCastException).
When I construct the graph using constructors only:
new ErrorHandlingProcessor<XyzContext>(
new RequestHandlingProcessor<XyzContext>(
new PersistingProcessor<XyzContext>(
new AbcProcessor()
)
)
);
then I, of course, get a compilation error that AbcContext is out of type bounds.
Is there some pattern or a way that it throws error during construction using the "dynamic" builder too? The builder would be used to create a Spring bean, so it would be awesome if it could throw an error during construction (app context would not start).
I know about type erasure, but was curious if there is some better way then using ugly nested constructors.
Aucun commentaire:
Enregistrer un commentaire