birdy
birdy

Reputation: 9646

How to abstract this piece of code so that I don't break the DRY principle

I've got two methods to extract data out of a CSV

private Car processCar (String [] row) {
    Car c = new Car();
    c.setCarId(Integer.parseInt(row[CSVColumns.carid.index]));
    ....
    return c;
}

private Driver processDriver(String[] row) {
    Driver d = new Driver(row[CSVColumns.name.index]);
    sys.setAge(row[CSVColumns.age.index]);
    ...
    return d;
}

Now, while extracting data out of the database I need to again make these two methods, however, with slight changes

private Car processCar (ResultSet rs) {
    Car c = new Car();
    c.setCarId(rs.getInt("Id"));
    ....
    return c;
}

private Driver processDriver(ResultSet) {
    Driver d = new Driver(rs.getString("Name));
    sys.setAge(rs.getString("Age"));
    .....
    return d;
}

Is there a way to simplify this so that I don't have to repeat myself? Additionally, each method contains a lot of getters/setters so I would hate to copy paste stuff and make changes...

Upvotes: 3

Views: 112

Answers (4)

Tomasz Nurkiewicz
Tomasz Nurkiewicz

Reputation: 340923

Abstract data source and use common interface

The idea is to have common adapter that translates either ResultSet or CSV into something common behind consistent interface:

interface CarDataSource {

    getCarId();
    //...

}

And two implementations:

class CsvDataSource implements CarDataSource {

    private final String[] row;

    public CsvDataSource(String[] row) {
        this.row = row;
    }

    public int getCarId() {
        return Integer.parseInt(row[CSVColumns.carid.index]);
    }

    //...

}

class ResultSetDataSource implements CarDataSource {

    private final ResultSet rs;

    public ResultSetDataSource(ResultSet rs) {
        this.rs = rs;
    }

    public int getCarId() {
        return Integer.parseInt(rs.getString("Name));
    }

    //...

}

and this is how you use it:

private Car processCar(CarDataSource s) {
    Car c = new Car();
    c.setCarId(s.getCarId());
    ....
    return c;
}

Providing either new CsvDataSource() or new ResultSetDataSource().

Abstract factory with two implementations:

Very similar approach, create and abstract factory:

interface CarFactory {

    Car create();

}

With two implementations:

class CsvCarFactory implements CarFactory {

    private final String[] row;

    public CsvDataSource(String[] row) {
        this.row = row;
    }

    public Car create() {
        Car c = new Car();
        c.setCarId(Integer.parseInt(row[CSVColumns.carid.index]));
        //...
        return c;
    }

}

class ResultSetCarFactory implements CarDataSource {

    private final ResultSet rs;

    public ResultSetCarFactory(ResultSet rs) {
        this.rs = rs;
    }

    public Car create() {
        Car c = new Car();
        c.setCarId(rs.getInt("Id"));
        //...
        return c;
    }

    //...

}

Combination

I don't like any of the approach above since they introduce a lot of boilerplate without adding much value. But the first approach is promising once you reduce it.

Have just one processCar() that takes String[] as an argument. However if you have a ResultSet, simply translate it first to String[] by extracting columns into subsequent items in arrays:

public String[] toStringArray(ResultSet rs)

The translation in the other direction so that ResultSet is the base format is also possible, but ResultSet is much harder to implement alone.

Upvotes: 1

christian.vogel
christian.vogel

Reputation: 2147

One solution could be to use an Adapter interface.


interface ParamAdapter {
    int getCarID();
    //...
}

class ResultSetParamAdapter implements ParamAdapter {

    private final ResultSet rs;

    public ResultSetParamAdapter(ResultSet rs) {
       this.rs = rs;
    }

    public int getCarID() {
        return rs.getInt("Id");
    }

    //...
}

class StringArrayParamAdapter implements ParamAdapter {

    private String[] array;

    public ResultSetParamAdapter(String[] array) {
       this.array = array;
    }

    public int getCarID() {
        return Integer.parseInt(array[CSVColumns.carid.index]);
    }

    //...
}

The three dots are the other methods which you COULD need.

Upvotes: 0

davec
davec

Reputation: 323

One option is to write a method that takes a result set and converts to return a string array. Depending on your database library, this may be largely built in. So you create something like

String[] ResultSetToStrings(ResultSet aResultSet)

Then your call looks something like either

processCar(["1994","Ford","Probe","etc"]);

or

processCar(ResultSetToStrings(aResultSet));

and you discard the ResultSet versions of your code.

This may incur some efficiency penalty (probably negligble - time spent converting to CSV and some extra string manipulation). Also be sure to make sure your CSV comes out in the right order (may need to explicitly state column names and order in your SQL query and.or consult the CSVColumns object in your conversion routine) and handle things like multiple rows, blanks, commas in the field values, etc.

The reverse might also work - change your String[] into a result set. In fact, again depending on your database driver, this might be built in.

Upvotes: 1

justin
justin

Reputation: 104698

another approach which can simplify aspects of your program:

private Car processCar (String [] row) {
    return new Car(Integer.parseInt(row[CSVColumns.carid.index]));
}

private Driver processDriver(String[] row) {
     return new Driver(row[CSVColumns.name.index], row[CSVColumns.age.index]);
}

Although you would often make your life easier by using types more distinct than strings, then allow them to convert to and from string representations. Then your parser could support creating these specialized types from the CSV input. So adding type safety can also go a long way to remove redundancy and make it more maintainable.

Upvotes: 0

Related Questions