The UserAccess and CustomerAccess classes each contain the method save
and find
. Here’s the code from one of them:
public void save(Persistable persistable) {
new Persister(this).save(persistable);
}
public Customer find(String idKey) {
return (Customer)new Persister(this).find(idKey);
}
In save
, the code looks exactly the same. The subtle difference is
that the method contains a reference to this
. I could easily push up
thesave
method to a common superclass, named perhaps DataAccess. That
superclass would then need to implement the PersistableMetadata
interface. There’s a minor nit: the name would bely the code contained
within–the save
method has nothing to do with metadata. I’m still
searching for more appropriate names.
The find
method is even more difficult, since it contains a cast to
the specific type being retrieved. If I move that method directly to the
superclass, its return type must change to Object. That would require
clients to cast, something that’s unacceptable. I could also provide a
subclass implementation with a variant return type, something else J2SE
5.0 supports, but then I’d have to provide a nuisance subclass method
that delegated to the superclass. So much for completely eliminating
duplication.
If you’re not working with J2SE 5.0, you’ll have to make the best of this. I’d probably not worry about it much. If you are using J2SE 5.0, you can use generics to eliminate the duplication. This requires a bunch of trivial changes to the code.
UserAccess
package domain;
import java.util.*;
public class UserAccess extends DataAccess {
public User create(List row) {
return new User(row.get(0), row.get(1));
}
public String getTable() {
return "userdata";
}
public String[] getColumns() {
return new String[] { User.NAME, User.PASSWORD };
}
public String getKeyColumn() {
return User.NAME;
}
}
The UserAccess class now extends the parameterized type DataAccess,
which will contain the save
and find
methods. The extends
in
UserAccess binds DataAccess to the User type. Note that the create
method directly returns the User type.
DataAccess
package domain;
abstract public class DataAccess implements PersistableMetadata {
public void save(Persistable persistable) {
new Persister(this).save(persistable);
}
protected T find(String idKey) {
return new Persister(this).find(idKey);
}
}
The DataAccess type is now parameterized. To avoid warnings, it’s also necessary to parameterize the Persister type, in addition to PersistableMetadata:
PersistableMetadata
package domain;
import java.util.*;
public interface PersistableMetadata {
String getTable();
String[] getColumns();
String getKeyColumn();
T create(List row);
}
Persister
package domain;
import java.util.*;
import persistence.*;
public class Persister {
private PersistableMetadata metadata;
private JdbcAccess access;
public Persister(PersistableMetadata metadata) {
this(metadata, new JdbcAccess());
}
public Persister(PersistableMetadata metadata, JdbcAccess access) {
this.metadata = metadata;
this.access = access;
}
public void save(Persistable persistable) {
String[] columns = metadata.getColumns();
String[] fields = new String[columns.length];
for (int i = 0; i < columns.length; i++)
fields[i] = persistable.get(columns[i]);
String sql = new SqlGenerator().createInsert(metadata.getTable(), columns, fields);
access.execute(sql);
}
public T find(String key) {
String sql = new SqlGenerator().createFindByKey(metadata.getTable(), metadata.getColumns(),
metadata.getKeyColumn(), key);
List row = access.executeQuery(sql);
return metadata.create(row);
}
}
The PersisterTest class also must change to eliminate warnings. I just bound everything to Object for the simplest solution.
I like the result so far. UserAccess and CustomerAccess are about as duplication-free as they’re going to get. Here’s the CustomerAccess class:
CustomerAccess
package domain;
import java.util.*;
public class CustomerAccess extends DataAccess {
public String getTable() {
return "cust";
}
public String[] getColumns() {
return new String[] { Customer.ID, Customer.NAME };
}
public String getKeyColumn() {
return Customer.ID;
}
public Customer create(List row) {
return new Customer(row.get(0), row.get(1));
}
}