Java inheritance, using builder pattern
I have 3 classes:
- Error
- ShellError
- WebError
where
ShellError extends Error
and
WebError extends Error
In ShellError
there are fields some of which are optional and others are required. I am building the object in the following manner:
shellError = new ShellError.Builder().setFile(filePattern)
.setHost(host).setPath(path).setSource(file.isSource())
.setJobName(p.getJobName()).build();
Since ShellError
extends Error
, I further:
shellError.setDescription(msg.toString());
shellError.setExceptionClass("MyEvilException");
shellError.setExceptionMessage("Some clever error message");
shellError.setStacktrace(stack);
So ... why bother with Builder? I like the fact that my build() amongst other things conv开发者_如何学Ceniently validates that all fields are set appropriately etc.
I would love it if I could .. build() ShellError
and add to it the fields from the Error
class.
What i did works.
- The question is:
Is there a better way, or does it make sense what I did?
-- EDIT
I updated Builder() to accept some of the parameters which were in Error class before. Now I have
shellError = new ShellError.Builder(exception, "Some description").setFile(filePattern).setHost(host)
.setPath(path).setSource(file.isSource()).
setJobName(p.getJobName()).build();
What do you say? Better? Worse?
The builder pattern, popularized by Josh Bloch, has several benefits, but it doesn't work so elegantly on parent/subclasses, as explained in this discussion by our colleagues in the C# world. The best solution I have seen so far is this one (or a slight variant of it).
Based on the functions you've referenced, this is clearly not the standard java.lang.Error class. Typically builders are used to allow for an immutable object to be easily constructed or to provide functionality similar to "named parameters" in cases where there are lots of configuration / construction parameters.
For this particular case, it would be more sensible if the Error class were immutable after construction, and if these additional setter functions were on the builder instead of on the error class. I don't know how much control you have over any of these classes, but if you can modify them, I would suggest first making the builder support the same setters, so you can do all the configuration at the builder. Then, if it is feasible to do so, you could try removing these setter methods and instead allowing these to be configured from the constructor. If you don't have any control at all over those, you can could potentially extend the builder class with another one which supports these additional methods.
What you did makes sense. It seems like the design of the builder and error classes don't necessarily make a whole lot of sense, forcing you to write code that feels inelegant or inconsistent.
As it was already said, the builder pattern is not something that could organically fit into the existing Java object initialization politics. There are several approaches to achieve the required result. Though, of course, it is always better to avoid any ambiguous practices, it's not always possible. My hack is based on Java reflection API with generics:
abstract public class AbstractClass {
public static class Builder {
public <T extends AbstractClass> T build(Class<T> implementingClass) {
try {
Constructor<T> constructor = implementingClass
.getConstructor(new Class[]{Builder.class});
return constructor.newInstance(this);
} catch (NoSuchMethodException e) {
// TODO handle the exception
} catch (InvocationTargetException | InstantiationException |
IllegalAccessException e) {
// TODO handle the exception
}
}
}
protected AbstractClass(Builder builder) {
}
}
public class ImplementingClass extends AbstractClass {
public ImplementingClass (Builder builder) {
super(builder);
}
}
The initialization:
ImplementingClass instance = new AbstractClass.Builder()
.build(ImplementingClass.class);
精彩评论