简体   繁体   中英

Refactoring methods that use the same code but different types

I have several methods that do the same thing yet, when interfacing with the MySQL database, save or load a different type of parameter. Currently, I have a different method for each type. How can I combine these methods so that they support different types?

Below is an example of two methods that are very similar yet use different types:

public static void saveLongArray(Connection con, int playerID, String tableName, String fieldName, long[] array, long[] originalArray) {
    try {
        for (int i = 0; i < array.length; i++) {
            // Check for change before running query
            if (array[i] != originalArray[i]) {
                if (array[i] != 0 && array[i] != -1) {
                    PreparedStatement updateQuery = con.prepareStatement("REPLACE INTO `" + tableName + "` (`player_id`, `index`, `" + fieldName + "`) VALUES(?, ?, ?)");
                    updateQuery.setInt(1, playerID);
                    updateQuery.setInt(2, i);
                    updateQuery.setLong(3, array[i]);
                    updateQuery.execute();
                } else {
                    PreparedStatement deleteQuery = con.prepareStatement("DELETE FROM `" + tableName + "` WHERE `player_id` = ? AND `index` = ?");
                    deleteQuery.setInt(1, playerID);
                    deleteQuery.setInt(2, i);
                    deleteQuery.execute();
                }

                originalArray[i] = array[i];
            }
        }
    } catch (SQLException ex) {
        Logger.getLogger(PlayerSaveHandler.class.getName()).log(Level.SEVERE, "SQL Exception while saving a long array!", ex);
    }
}

public static void saveIntArray(Connection con, int playerID, String tableName, String fieldName, int[] array, int[] originalArray) {
    try {
        for (int i = 0; i < array.length; i++) {
            // Check for change before running query
            if (array[i] != originalArray[i]) {
                if (array[i] != 0 && array[i] != -1) {
                    PreparedStatement updateQuery = con.prepareStatement("REPLACE INTO `" + tableName + "` (`player_id`, `index`, `" + fieldName + "`) VALUES(?, ?, ?)");
                    updateQuery.setInt(1, playerID);
                    updateQuery.setInt(2, i);
                    updateQuery.setInt(3, array[i]);
                    updateQuery.execute();
                } else {
                    PreparedStatement deleteQuery = con.prepareStatement("DELETE FROM `" + tableName + "` WHERE `player_id` = ? AND `index` = ?");
                    deleteQuery.setInt(1, playerID);
                    deleteQuery.setInt(2, i);
                    deleteQuery.execute();
                }

                originalArray[i] = array[i];
            }
        }
    } catch (SQLException ex) {
        Logger.getLogger(PlayerSaveHandler.class.getName()).log(Level.SEVERE, "SQL Exception while saving an int array!", ex);
    }
}

Note in that example the types are both numerical. In a case where types are completely different (eg int and String), what could I do to avoid have near-duplicate methods?

You can apply the Strategy pattern here.

interface TypeDependentBehavior<T> {
   void setFieldValue(PreparedStatement st, T value);
}

interface StringBehavior extends TypeDependentBehavior<String> {
   void setFieldValue(PreparedStatement st, String value) {
     st.setString(3, value);
   }
}    

interface IntBehavior extends TypeDependentBehavior<Integer> {
   void setFieldValue(PreparedStatement st, Integer value) {
     st.setInt(3, value);
   }
}

...

public static void saveArray<T>(Connection con, int playerID, String tableName, String fieldName, T[] array, T[] originalArray, TypeDependentBehavior<T> behavior) {
 try {
        for (int i = 0; i < array.length; i++) {
            // Check for change before running query
            if (array[i] != originalArray[i]) {
                if (array[i] != 0 && array[i] != -1) {
                    PreparedStatement updateQuery = con.prepareStatement("REPLACE INTO `" + tableName + "` (`player_id`, `index`, `" + fieldName + "`) VALUES(?, ?, ?)");
                    updateQuery.setInt(1, playerID);
                    updateQuery.setInt(2, i);
                    behavior.setFieldValue(updateQuery, array[i]);
                    updateQuery.execute();
                } else {
                    PreparedStatement deleteQuery = con.prepareStatement("DELETE FROM `" + tableName + "` WHERE `player_id` = ? AND `index` = ?");
                    deleteQuery.setInt(1, playerID);
                    deleteQuery.setInt(2, i);
                    deleteQuery.execute();
                }

                originalArray[i] = array[i];
            }
        }
    } catch (SQLException ex) {
        Logger.getLogger(PlayerSaveHandler.class.getName()).log(Level.SEVERE, "SQL Exception while saving an int array!", ex);
    }
}

I would just use long[] instead of int[] . The memory difference is very small compared with the cost of using JDBC.

If you need to handle String you can use an object type.

public static void saveArray(Connection con, int playerID, String tableName, 
    String fieldName, Object[] array, Object[] originalArray) {

If you want one method for long[] and Object[] you can use the Array.getLength() and Array.get() method to access all array types generically. This could add more complexity than it saves.

You can use generics for that, for example

void doSomething(int[] array) {
    for (int i = 0; i < array.length; i++)
        System.out.println(array[i]);
}

void doSomething(long[] array) {
    for (int i = 0; i < array.length; i++)
        System.out.println(array[i]);
}

can be generalized into

<T> void doSomething(T[] array) {
    for (int i = 0; i < array.length; i++)
        System.out.println(array[i]);
}

Now you can call

int[] array1 = new int[] { 1, 2, 3 };
doSomething(array1);

long[] array2 = new long[] { 1L, 2L, 3L };
doSomething(array2);

String[] array3 = new String[] { "one", "two", "three" };
doSomething(array3);

But you should check your method implementation and make sure that it will still work with any array type, especially the SQL statement.

What if you broke out your comparative functionality and had your methods down to the most granular level? For example:

public static void update(Connection con, int playerID, String tableName, String fieldName, String value) {
    // update query logic here
}

And the same for delete() . There is no reason to pass both the "new" and "original" values into this function and do the compare inside. I suggest looping through the arrays, comparing, and calling either update() or delete() based on your needs. To deal with different data types, I would always pass in the String value of what you want in the database.

With similar types, you could create a wrapper - a method which takes as an argument an int[] , generates a long[] from the values passed and calls the method variant which takes long[] as argument to perform the actual work. It has some overhead, but assuming your arrays are not millions of entries long, it's negligible compared to the cost of communication with the database.

With completely different types, you could try using Object[] (or maybe somehow use generics), but there would be some pitfalls. You would need to use a different deletion marker instead of 0 or -1 ( null seems the obvious choice). The bigger issue is setting parameters in PreparedStatement since different methods need to be called but you could generate the whole query string manually, using the provided objects' toString() methods instead of setting parameters with setInt() etc.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM