java - Which pattern to use to avoid code duplication with object value transformer -
i want rid of following code duplication within myfacadebean. consider 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); } with 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 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 following entities:
@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 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); } } problems assemblerfactoryimpl , assemblerimpl in have instanceof checks , castings...
another idea let value know transformer use (or how transform). want value "dumb".
@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; } does not work, because of
type mismatch: cannot convert firstassembler abstractassembler
i'm posting separate answer, since don't think there's wrong having save method every abstractvalue type.
first we'll establish base value class example. i'm using interface don't muddy waters. abstractvalue interface:
public interface abstractvalue { int getuniqueid(); double getnominalvalue(); <t> t accept(abstractvaluevisitor<t> visitor); } and "visitor interface":
public interface abstractvaluevisitor<t> { t visit(firstvalue value); t visit(secondvalue value); t visit(thirdvalue value); t visit(fourthvalue value); } i know don't want intelligence baked abstractvalue, going add one specification... concrete implementations of abstractvalue (all four) implement accept method way:
@override public <t> t accept(abstractvaluevisitor<t> visitor) { return visitor.visit(this); } so method implemented four times: in 4 value classes, same way. because visitor interface aware of concrete implementations, appropriate method called each particular value type. 3 of these parts put "visitor pattern".
now we'll make entity factory. job create appropriate abstractentity when provided 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 properties specific firstentity entity.setfixedrate(value.getfixedrate()); entity.setstartdate(value.getstartdate()); return entity; } @override public abstractentity visit(secondvalue value) { secondentity entity = new secondentity(); // set properties specific secondentity entity.settransactiontype(value.gettransactiontype()); entity.setvaluedate(value.getvaluedate()); return entity; } @override public abstractentity visit(thirdvalue value) { thirdentity entity = new thirdentity(); // set properties specific thirdentity return entity; } @override public abstractentity visit(fourthvalue value) { fourthentity entity = new fourthentity(); // set properties specific fourthentity return entity; } } now facade implementation takes abstractvalue, , got 1 save method you're 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); } } because abstractvalue follows visitor pattern, can sorts of polymorphic behavior. such as:
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 firstvalue!"); print("being firstvalue groovy!"); return null; } @override public void visit(secondvalue value) { print("i'm secondvalue!"); print("being secondvalue awesome!"); return null; } @override public void visit(thirdvalue value) { print("i'm thirdvalue!"); print("meh."); return null; } @override public void visit(fourthvalue value) { print("i'm thirdvalue!"); print("derp."); return null; } } in example, visitor isn't returning anything... it's "doing" something, we'll set return value void, since it's non-instantiatable. print value simply:
// (value must not null) value.accept(new abstractvalueprinter(system.out)); finally, coolest part of visitor pattern (in opinion): add fifthvalue. add new method visitor interface:
t visit(fifthvalue value); and suddenly, you can't compile. must address lack of handling in 2 places: abstractentityfactory , abstractvalueprinter. great, because should consider in places. doing class comparisons (with either instanceof or rinde's solution of class-to-factory map) "miss" new value type, , have runtime bugs... if you're doing 100 different things these value types.
anyhoo, didn't want this, there go :)
Comments
Post a Comment