Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

I have two Java classes with very similar code. Basically they are almost the same excpept for a few method calls etc, i.e. replace 'ip' with 'msisdn' in the classes and they would be identical.

I'm wondering how to factor them to remove the duplication? Or maybe if there is a Design Pattern I should be using?

Here are the two classes.

public class IpQuery {

    private final CommandLineInterface commandLineInterface;

    private final CSVFileReader csvFileReader;

    private final XMLFileReader xmlFileReader;

    private final CacheQueryRequester cacheQueryRequester;

    private final CacheQueryResponseParser cacheQueryResponseParser;

    /**
     * Invokes a cache query using the ip as a key into the session cache, displaying the result on the command line.
     * <p>
     * @param   ipToQueryCacheFor the ip to query the PCC-A session cache for.
     * @param   commandLineInterface object used to display error/warning/info messages on the command line.    
     */
    public IpQuery(String ipToQueryCacheFor, CommandLineInterface commandLineInterface) {
        this.commandLineInterface = commandLineInterface;

        this.csvFileReader = new CSVFileReader(commandLineInterface);

        this.xmlFileReader = new XMLFileReader(commandLineInterface);

        this.cacheQueryRequester = new CacheQueryRequester();

        this.cacheQueryResponseParser = new CacheQueryResponseParser();

        queryCacheForSessionWithIp(ipToQueryCacheFor);
    }

    private void queryCacheForSessionWithIp(String ipToQueryCacheFor) { 
        final String configFile = commandLineInterface.getConfigFileArgValue(); 

        if (configFile != null) {

            if (configFile.endsWith(".xml")) {

                final ArrayList<String> pccaPoolHosts = xmlFileReader.read(configFile);

                queryPrimaryAndSecondaryPCCAForIp(ipToQueryCacheFor, pccaPoolHosts);
            } else {
                final ArrayList<String> pccaPoolHosts = csvFileReader.read(configFile);

                queryPrimaryAndSecondaryPCCAForIp(ipToQueryCacheFor, pccaPoolHosts);
            }
        } else {
            commandLineInterface.displayMessage(
                    "Warning: No CSV or XML config file entered, defaulting to querying localhost [::1].");

            invokeCacheQueryForIp(ipToQueryCacheFor, "::1");
        }
    }

    private void queryPrimaryAndSecondaryPCCAForIp(String ipToQueryCacheFor, ArrayList<String> pccaPoolHosts) {
        final PCCAServerSelector pccaServerSelector = new PCCAServerSelector(pccaPoolHosts, ipToQueryCacheFor);

        commandLineInterface.displayMessage(
                "Info: Querying primary PCC-A [" + pccaServerSelector.getPrimaryEndpoint().getIPAddress() +"]");

        invokeCacheQueryForIp(ipToQueryCacheFor, pccaServerSelector.getPrimaryEndpoint().getIPAddress());

        commandLineInterface.displayMessage(
                "Info: Querying secondary PCC-A [" + pccaServerSelector.getSecondaryEndpoint().getIPAddress() +"]");

        invokeCacheQueryForIp(ipToQueryCacheFor, pccaServerSelector.getSecondaryEndpoint().getIPAddress());
    }

    private void invokeCacheQueryForIp(String ipToQueryCacheFor, String pccaHostToQuery) {
        final PayLoad cacheQueryResponsePayLoad = cacheQueryRequester.queryCacheForSessionWithIp(ipToQueryCacheFor, pccaHostToQuery);

        final String cacheQueryResponseAsString = cacheQueryResponseParser.parse(cacheQueryResponsePayLoad);

        commandLineInterface.displayMessage(cacheQueryResponseAsString);
    }
}





public class MsisdnQuery {

    private final CommandLineInterface commandLineInterface;

    private final CSVFileReader csvFileReader;

    private final XMLFileReader xmlFileReader;

    private final CacheQueryRequester cacheQueryRequester;

    private final CacheQueryResponseParser cacheQueryResponseParser;

    /**
     * Invokes a cache query using the msisdn as a key into the session cache, displaying the result on the command line.
     * <p>
     * @param   msisdnToQueryCacheFor the msisdn to query the PCC-A session cache for.
     * @param   commandLineInterface object used to display error/warning/info messages on the command line.    
     */
    public MsisdnQuery(String msisdnToQueryCacheFor, CommandLineInterface commandLineInterface) {
        this.commandLineInterface = commandLineInterface;

        this.csvFileReader = new CSVFileReader(commandLineInterface);

        this.xmlFileReader = new XMLFileReader(commandLineInterface);

        this.cacheQueryRequester = new CacheQueryRequester();

        this.cacheQueryResponseParser = new CacheQueryResponseParser();

        queryCacheForSessionWithMsisdn(msisdnToQueryCacheFor);
    }

    private void queryCacheForSessionWithMsisdn(String msisdnToQueryCacheFor) {     
        final String configFile = commandLineInterface.getConfigFileArgValue(); 

        if (configFile != null) {

            if (configFile.endsWith(".xml")) {

                final ArrayList<String> pccaPoolHosts = xmlFileReader.read(configFile);

                queryPrimaryAndSecondaryPCCAForMsisdn(msisdnToQueryCacheFor, pccaPoolHosts);
            } else {
                final ArrayList<String> pccaPoolHosts = csvFileReader.read(configFile);

                queryPrimaryAndSecondaryPCCAForMsisdn(msisdnToQueryCacheFor, pccaPoolHosts);
            }
        } else {
            commandLineInterface.displayMessage(
                    "Warning: No CSV or XML config file entered, defaulting to querying localhost [::1].");

            invokeCacheQueryForMsisdn(msisdnToQueryCacheFor, "::1");
        }
    }

    private void queryPrimaryAndSecondaryPCCAForMsisdn(String msisdnToQueryCacheFor, ArrayList<String> pccaPoolHosts) {
        final PCCAServerSelector pccaServerSelector = new PCCAServerSelector(pccaPoolHosts, msisdnToQueryCacheFor);

        commandLineInterface.displayMessage(
                "Info: Querying primary PCC-A [" + pccaServerSelector.getPrimaryEndpoint().getIPAddress() +"]");

        invokeCacheQueryForMsisdn(msisdnToQueryCacheFor, pccaServerSelector.getPrimaryEndpoint().getIPAddress());

        commandLineInterface.displayMessage(
                "Info: Querying secondary PCC-A [" + pccaServerSelector.getSecondaryEndpoint().getIPAddress() +"]");

        invokeCacheQueryForMsisdn(msisdnToQueryCacheFor, pccaServerSelector.getSecondaryEndpoint().getIPAddress());
    }

    private void invokeCacheQueryForMsisdn(String msisdnToQueryCacheFor, String pccaHostToQuery) {
        final PayLoad cacheQueryResponsePayLoad 
            = cacheQueryRequester.queryCacheForSessionWithMsisdn(msisdnToQueryCacheFor, pccaHostToQuery);

        final String cacheQueryResponseAsString = cacheQueryResponseParser.parse(cacheQueryResponsePayLoad);

        commandLineInterface.displayMessage(cacheQueryResponseAsString);
    }
}

Many thanks!

share|improve this question
    
I think it would be useful to remove the multiple blank lines from the above –  Brian Agnew Aug 1 '12 at 13:00
add comment

2 Answers

up vote 1 down vote accepted

If I'm right the only difference is this line:

final PayLoad cacheQueryResponsePayLoad 
        = cacheQueryRequester.queryCacheForSessionWithMsisdn(msisdnToQueryCacheFor, 
            pccaHostToQuery);

ant the CacheQueryRequester class could be similar to the following:

public class CacheQueryRequester {

    public PayLoad queryCacheForSessionWithMsisdn(final String msisdnToQueryCacheFor, 
            final String pccaHostToQuery) {
        ...
    }

    public PayLoad queryCacheForSessionWithIp(final String ipToQueryCacheFor, 
            final String pccaHostToQuery) {
        ...
    }

}

To remove the duplication I'd change the CacheQueryRequester to an interface with only one method:

public class CacheQueryRequester {

    public PayLoad queryCacheForSession(final String queryCacheFor, 
            final String pccaHostToQuery) {
        ...
    }

}

And create two subclasses: IpCacheQueryRequester and MsisdnCacheQueryRequester. Finally, rename one of the original query classes simply to Query and modify its constructor to the following:

public Query(final String queryCacheFor, final CommandLineInterface commandLineInterface, 
        final CacheQueryRequester cacheQueryRequester) {
    ...
    this.cacheQueryRequester = cacheQueryRequester;
    ...
}

Now, you can pass an IpCacheQueryRequester or an MsisdnCacheQueryRequester instance to the constructor and get rid of the other query class.

share|improve this answer
add comment

From memory I think Eclipse does an "Extract Supertype" which might help you pull out a base class. Using inheritance may not be the best way of solving this but it will at least give you the common code.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.