Database TDD Part 15: Eliminating Duplication With Generics

by Jeff Langr

November 02, 2005

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));
       }
    }

Share your comment

Jeff Langr

About the Author

Jeff Langr has been building software for 40 years and writing about it heavily for 20. You can find out more about Jeff, learn from the many helpful articles and books he's written, or read one of his 1000+ combined blog (including Agile in a Flash) and public posts.