I am posting this as a separate answer, since I really don't think that something is wrong with the save method for each type of AbstractValue.
First we set up your base value class for this example. I use the interface so that we do not mutate the water. Your AbstractValue interface:
public interface AbstractValue { int getUniqueId(); double getNominalValue(); <T> T accept(AbstractValueVisitor<T> visitor); }
And the "visitor interface":
public interface AbstractValueVisitor<T> { T visit(FirstValue value); T visit(SecondValue value); T visit(ThirdValue value); T visit(FourthValue value); }
I know that you do not want the intelligence to be baked in AbstractValue , but we are going to add one specification ... that all concrete implementations of AbstractValue (all four) implement the accept method in this way:
@Override public <T> T accept(AbstractValueVisitor<T> visitor) { return visitor.visit(this); }
Thus, this method is implemented four times: in all four classes of values ββin exactly the same way. Since the visitor interface knows all the specific implementations, the corresponding method will be called for each particular type of value. All three of these parts are combined, this is the "visitor template".
Now we will create a factory entity. Its task is to create the corresponding AbstractEntity subject to AbstractValue :
public class AbstractEntityFactory implements AbstractValueVisitor<AbstractEntity> { private static final AbstractEntityFactory INSTANCE; static { INSTANCE = new AbstractEntityFactory(); }
Now your facade implementation accepts AbstractValue , and you have the save method you are looking for:
public class FacadeBean implements Facade { @EJB private CrudService crudService; @Override public void save(AbstractValue value) { AbstractEntity entity = AbstractEntityFactory.create(value); crudService.persist(entity); } }
Since your AbstractValue now follows the visitor pattern, you can perform all kinds of polymorphic behavior. For instance:
public class AbstractValuePrinter implements AbstractValueVisitor<Void> { private final Appendable out; public AbstractValuePrinter(Appendable out) { this.out = out; } private void print(String s) { try { out.append(s); out.append('\n'); } catch (IOException e) { throw new IllegalStateException(e); } } @Override public Void visit(FirstValue value) { print("I'm a FirstValue!"); print("Being a FirstValue is groovy!"); return null; } @Override public Void visit(SecondValue value) { print("I'm a SecondValue!"); print("Being a SecondValue is awesome!"); return null; } @Override public Void visit(ThirdValue value) { print("I'm a ThirdValue!"); print("Meh."); return null; } @Override public Void visit(FourthValue value) { print("I'm a ThirdValue!"); print("Derp."); return null; } }
In this example, this visitor returns nothing ... it "does" something, so we just set the return value to Void , since it is not possible. Then you print the value simply:
// (value must not be null) value.accept(new AbstractValuePrinter(System.out));
Finally, the coolest part of the visitor pattern (in my opinion): you add FifthValue . You add a new method to your visitor interface:
T visit(FifthValue value);
And suddenly you cannot compile . You must eliminate the disadvantage of this processing in two places: AbstractEntityFactory and AbstractValuePrinter . This is great because you have to consider this in these places. Performing class mappings (using instanceof or rinde-resolving a class map with factory) will most likely skip the new value type, and now you have runtime errors ... especially if you do 100 different things with these value types.
Anyhoo, I didnβt want to delve into this, but there you go :)