Which template to use to avoid duplication of code with a transformer of the value of the object

I want to get rid of the following code duplication in MyFacadeBean . Consider the following situation:

 public class FacadeBean implements Facade { @EJB private CrudService crudService; @Inject private FirstAssembler firstAssembler; @Inject private SecondAssembler secondAssembler; @Inject private ThirdAssembler thridAssembler; @Inject private FourthAssembler fourthAssembler; @Override public void save(FirstValue value) { FirstEntity entity = this.firstAssembler.transformToEntity(value); this.crudService.persist(entity); } @Override public void save(SecondValue value) { SecondEntity entity = this.secondAssembler.transformToEntity(value); this.crudService.persist(entity); } @Override public void save(ThirdValue value) { ThirdEntity entity = this.thirdAssembler.transformToEntity(value); this.crudService.persist(entity); } @Override public void save(FourthValue value) { FourthEntity entity = this.fourthAssembler.transformToEntity(value); this.crudService.persist(entity); } } public interface MyFacade { void save(FirstValue value); void save(SecondValue value); } 

Using CrudService :

 public interface CrudService { void persist(Object entity); } @Stateless @Local(CrudService.class) @TransactionAttribute(TransactionAttributeType.MANDATORY) public class CrudServiceBean implements CrudService { public static final String PERSISTENCE_UNIT_NAME = "my_persistence_unit"; private EntityManager entityManager; @PersistenceContext(unitName = PERSISTENCE_UNIT_NAME) public void setEntityManager(EntityManager entityManager) { this.entityManager = entityManager; } @Override public void persist(Object entity) { this.entityManager.persist(entity); } } 

With the following assemblers:

 public class FirstAssembler extends AbstractAssembler<FirstEntity> { public FirstEntity transformToEntity(FirstValue value) { if (value == null) return null; FirstEntity entity = new FirstEntity(); transformAbstractValueToAbstractObject(value, entity); entity.setFixedRate(value.getFixedRate()); entity.setStartDate(value.getStartDate()); return entity; } } public class SecondAssembler extends AbstractAssembler<SecondEntity> { public SecondEntity transformToEntity(SecondValue value) { if (value == null) return null; SecondEntity entity = new SecondEntity(); transformAbstractValueToAbstractObject(value, entity); entity.setTransactionType(value.getTransactionType()); entity.setValueDate(value.getValueDate()); return entity; } } public abstract class AbstractAssembler<T extends AbstractEntity> { protected void transformAbstractValueToAbstractObject(AbstractValue value, T object) { object.setUniqueId(value.getUniqueId()); object.setNominalAmountValue(value.getNominalAmountValue()); } } 

With the following objects:

 @Entity public class FirstEntity extends AbstractEntity { private static final long serialVersionUID = 1L; @Id @Column(name = "ID") private Long id; @Column(name = "START_DATE") @Temporal(TemporalType.DATE) private Date startDate; @Column(name = "FIXED_RATE") @Digits(integer = 1, fraction = 10) private BigDecimal fixedRate; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public Date getStartDate() { return startDate; } public void setStartDate(Date startDate) { this.startDate = startDate; } public BigDecimal getFixedRate() { return fixedRate; } public void setFixedRate(BigDecimal fixedRate) { this.fixedRate = fixedRate; } } @Entity public class SecondEntity extends AbstractEntity { private static final long serialVersionUID = 1L; @Id @Column(name = "ID") private Long id; @Column(name = "VALUE_DATE") @Temporal(TemporalType.DATE) private Date valueDate; @Column(name = "TRANSACTION_TYPE") @Enumerated(EnumType.STRING) private TransactionType transactionType; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public Date getValueDate() { return valueDate; } public void setValueDate(Date valueDate) { this.valueDate = valueDate; } public TransactionType getTransactionType() { return transactionType; } public void setTransactionType(TransactionType transactionType) { this.transactionType = transactionType; } } @MappedSuperclass public abstract class AbstractEntity implements Serializable { private static final long serialVersionUID = 1L; @Column(name = "TRANSACTION_NOM_AMOUNT_VALUE") @Digits(integer = 18, fraction = 5) @Min(0) private BigDecimal nominalAmountValue; public BigDecimal getNominalAmountValue() { return nominalAmountValue; } public void setNominalAmountValue(BigDecimal nominalAmountValue) { this.nominalAmountValue = nominalAmountValue; } } 

I tried the following approach:

 public class FacadeBean implements Facade { @Inject private Assembler assembler; @Inject private AssemblerFactory assemblerFactory; @Override public <T extends AbstractValue> void save(T value) { Assembler assembler = assemblerFactory.createAssembler(value); AbstractEntity entity = assembler.transformToEntity(value); this.crudService.persist(entity); } } 

The problems are AssemblerFactoryImpl and AssemblerImpl, in which I have to do instance checks and castings ...

Another idea is to let know which transformer to use (or how to convert). But I want the meaning to be "stupid."

@Glenn Lane

 public AbstractValue save(AbstractValue value) { AbstractAssembler<AbstractValue, AbstractEntity> assembler = new FirstAssembler(); AbstractEntity entity = assembler.transformToEntity(value); AbstractValue result = assembler.transformToValue(entity); return result; } 

not working due

Type mismatch: cannot convert from FirstAssembler to AbstractAssembler

+5
source share
5 answers

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(); } // Singleton pattern private AbstractEntityFactory() { } public static AbstractEntity create(AbstractValue value) { if (value == null) { return null; } AbstractEntity e = value.accept(INSTANCE); e.setNominalValue(value.getNominalValue()); e.setUniqueId(value.getUniqueId()); return e; } @Override public AbstractEntity visit(FirstValue value) { FirstEntity entity = new FirstEntity(); // Set all properties specific to FirstEntity entity.setFixedRate(value.getFixedRate()); entity.setStartDate(value.getStartDate()); return entity; } @Override public AbstractEntity visit(SecondValue value) { SecondEntity entity = new SecondEntity(); // Set all properties specific to SecondEntity entity.setTransactionType(value.getTransactionType()); entity.setValueDate(value.getValueDate()); return entity; } @Override public AbstractEntity visit(ThirdValue value) { ThirdEntity entity = new ThirdEntity(); // Set all properties specific to ThirdEntity return entity; } @Override public AbstractEntity visit(FourthValue value) { FourthEntity entity = new FourthEntity(); // Set all properties specific to FourthEntity return entity; } } 

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 :)

+3
source

Use a generic method with a parameter of a related type to save yourself the repetition:

public <T extends AbstractValue> T save(T value) {...}

Inside the body of the method, you can reference the value argument with all methods related to AbstractValue .

Notes

  • Since your save methods seem to be overrides in this example, you may need to modify the design of the parent class or interface.
  • You can also use a generic class to start with (instead of a generic method in an optional generic class), depending on your use case.
+2
source

I think the problem in your code is that the generic type AbstractAssembler is the output type of the conversion method, not the input. If you change it as follows:

 public abstract class AbstractAssembler<T extends AbstractValue> { protected void transformAbstractValueToAbstractObject(AbstractEntity entity, T value) { entity.setUniqueId(value.getUniqueId()); entity.setNominalAmountValue(value.getNominalAmountValue()); } public abstract AbstractEntity transformToEntity(T value); } 

You can then change FacadeBean to the following.

 public class FacadeBean { @EJB private CrudService crudService; final Map<Class<?>, AbstractAssembler<?>> knownAssemblers; FacadeBean() { knownAssemblers = new LinkedHashMap<>(); knownAssemblers.put(FirstValue.class, new FirstAssembler()); knownAssemblers.put(SecondValue.class, new SecondAssembler()); // add more assemblers here } public <T extends AbstractValue> void save(T value, Class<T> type) { @SuppressWarnings("unchecked") // safe cast final AbstractAssembler<T> assembler = (AbstractAssembler<T>) knownAssemblers.get(type); final AbstractEntity entity = assembler.transformToEntity(value); this.crudService.persist(entity); } } 

Please note that I changed the signature of the save(..) method so that we have the type of object that needs to be saved. With this type, we can simply find the appropriate assembler that should be used. And since assembler is now common to its input type, we can do a safe listing (be careful that your card is consistent).

This implementation avoids code duplication since you only need one save method. The use of the instanceof operator is prevented by changing the general type of AbstractAssembler and storing all collectors on the map.

Assemblers may look like this:

 public class FirstAssembler extends AbstractAssembler<FirstValue> { @Override public FirstEntity transformToEntity(FirstValue value) { final FirstEntity entity = new FirstEntity(); // do transformational stuff super.transformAbstractValueToAbstractObject(entity, value); entity.setFixedRate(value.getFixedRate()); entity.setStartDate(value.getStartDate()); return entity; } } public class SecondAssembler extends AbstractAssembler<SecondValue> { @Override public SecondEntity transformToEntity(SecondValue value) { final SecondEntity entity = new SecondEntity(); // do transformational stuff super.transformAbstractValueToAbstractObject(entity, value); return entity; } } 

Note I am not familiar with Java beans, so you probably have to adapt the code a bit if you want to use @Inject ed assemblers instead of calling constructors directly.

0
source

Here you come closer to gold, but you can reduce a bit, in particular, null -check and call the general method of setting fields from each extension.

 public abstract class AbstractAssembler<V extends AbstractValue, E extends AbstractEntity> { public final E transformToEntity(V value) { if (value == null) { return null; } E entity = createEntity(value); entity.setUniqueId(value.getUniqueId()); entity.setNominalAmountValue(value.getNominalAmountValue()); return entity; } /** * @return * Appropriate entity object, with the fields not common to all AbstractEntity * already set */ protected abstract E createEntity(V value); } 

And then extended assembler:

 public class FirstAssembler extends AbstractAssembler<FirstValue, FirstEntity> { @Override protected FirstEntity createEntity(FirstValue value) { FirstEntity entity = new FirstEntity(); entity.setFixedRate(value.getFixedRate()); entity.setStartDate(value.getStartDate()); return entity; } } 

If you really need one factory class to handle all your values ​​/ entities, I would consider a visitor template extended with a type parameter in the visitor interface (and the accept / entity / accept methods return a type based on the visit interface). I will not give an example here simply because I do not think this is justified in your case.

0
source

You may have one save method in terms of classes that store these values, but you still have to implement three separate save methods.

Implement a class with all three save methods. For instance:

 public class ValuePersister { @Inject private Assembler1 assembler1; @Inject private Assembler2 assembler2; @Inject private Assembler3 assembler3; public Value1 save(Value1 value1, CrudService crudService) { Entity1 entity1 = assembler1.transformToObject(value1); crudService.persist(entity1); return assembler1.transformToValue(entity1); } public Value2 save(Value2 value2, CrudService crudService) { Entity2 entity2 = assembler2.transformToObject(value2); crudService.persist(entity2); return assembler2.transformToValue(entity2); } public Value3 save(Value3 value3, CrudService crudService) { Entity3 entity3 = assembler3.transformToObject(value3); crudService.persist(entity3); return assembler3.transformToValue(entity3); } } 

Add abstract method to AbstractValue:

 public abstract AbstractValue save(ValuePersister valuePersister, CrudService crudService); 

Implement this method in each class that extends AbstractValue:

 @Override public AbstractValue save(ValuePersister valuePersister, CrudService crudService) { return valuePersister.save(this, crudService); } 

Add a ValuePersister and implement your original generic save method:

 @Inject private ValuePersister valuePersister; @Override public AbstractValue save(AbstractValue value) { return value.save(valuePersister, crudService) } 
-1
source

Source: https://habr.com/ru/post/1241637/


All Articles