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 the save 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.
package domain;
import java.util.*;
public class UserAccess extends DataAccess<User> {
public User create(List<String> 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.
package domain;
abstract public class DataAccess<T> implements PersistableMetadata<T> {
public void save(Persistable persistable) {
new Persister<T>(this).save(persistable);
}
protected T find(String idKey) {
return new Persister<T>(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:
package domain;
import java.util.*;
public interface PersistableMetadata<T> {
String getTable();
String[] getColumns();
String getKeyColumn();
T create(List<String> row);
}
package domain;
import java.util.*;
import persistence.*;
public class Persister<T> {
private PersistableMetadata<T> metadata;
private JdbcAccess access;
public Persister(PersistableMetadata<T> metadata) {
this(metadata, new JdbcAccess());
}
public Persister(PersistableMetadata<T> 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<String> 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:
package domain;
import java.util.*;
public class CustomerAccess extends DataAccess<Customer> {
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<String> row) {
return new Customer(row.get(0), row.get(1));
}
}
February 2004 March 2004 May 2004 September 2004 October 2004 January 2005 February 2005 September 2005 October 2005 November 2005 December 2005 January 2006 February 2006 March 2006 June 2006 August 2006 January 2007 February 2007 March 2007 April 2007 September 2007 October 2007 November 2007 December 2007 January 2008