Search code examples
javajava-8coding-stylerefactoringcode-duplication

How best to refactor variable instantiation duplication in Java?


I have two methods in Java that collect different information, but they both set up and run the same process which we then collect information from - and the collection of the data happens inside a loop, and the variables we instantiate are also used in the loop.

 Map<Integer, Integer> getResponsesWithCount(int baseCostMultiplier, int reels, int visibleSymbols, String stakeCostUuid, int totalBets) throws InsufficientFundsException {
    final int stake = getStake(baseCostMultiplier, stakeCostUuid);
    long balance = 10L * stake;
    final TestGpasPlatform testGpasPlatform = TestGpasPlatform.create(ryotaAdapter, TestGpasPlatform.DEFAULT_MIN_BET, Math.max(stake, TestGpasPlatform.DEFAULT_MAX_BET), TestGpasPlatform.DEFAULT_MAX_WIN, ImmutableList.of(baseCostMultiplier));

    final Map<Integer, Integer> responseCounts = new HashMap<>();
    for (int count = 0; count < totalBets; count++) {
        final Tuple2<List<Output>, TestGpasPlatform> result = playWithRealRng(baseCostMultiplier, count, reels, visibleSymbols, stakeCostUuid, testGpasPlatform);
        // If we run out of balance, re-start, we want to do meaningful spins that trigger features, etc
        balance = checkBalance(stake, balance, result._1(), count);
        final int byteLength = result._2.getLastResponse().map(s -> s.getBytes().length).orElse(0);
        responseCounts.putIfAbsent(byteLength, 0);
        responseCounts.put(byteLength, responseCounts.get(byteLength) + 1);
    }
    return responseCounts;
}

Map<Integer, Integer> getResponsesWithPayouts(int baseCostMultiplier, int reels, int visibleSymbols, String stakeCostUuid, int totalBets) throws InsufficientFundsException{
    final int stake = getStake(baseCostMultiplier, stakeCostUuid);
    long balance = 10L * stake;
    final TestGpasPlatform testGpasPlatform = TestGpasPlatform.create(ryotaAdapter, TestGpasPlatform.DEFAULT_MIN_BET, Math.max(stake, TestGpasPlatform.DEFAULT_MAX_BET), TestGpasPlatform.DEFAULT_MAX_WIN, ImmutableList.of(baseCostMultiplier));
    final Map<Integer, Integer> responseCounts = new HashMap<>();
    for (int count = 0; count < totalBets; count++) {
        final Tuple2<List<Output>, TestGpasPlatform> result = playWithRealRng(baseCostMultiplier, count, reels, visibleSymbols, stakeCostUuid, testGpasPlatform);

        // If we run out of balance, re-start, we want to do meaningful spins that trigger features, etc
        balance = checkBalance(stake, balance, result._1(), count);

        final int byteLength = result._2.getLastResponse().map(s -> s.getBytes().length).orElse(0);
        final PlayData playData = result._2.getLastResponse().map(s -> new Gson().fromJson(s, GdkPlayData.class)).orElse(new GdkPlayData());
        final java.util.List<SlotsActionData> actionData = playData.findLastPlay().getLastPlayInModeData().getSlotsData().getActions();
        final int sumOfPayouts = actionData.stream()
                                           .map(SlotsActionData::getPayouts)
                                           .mapToInt(java.util.List::size)
                                           .sum();
        responseCounts.putIfAbsent(byteLength, sumOfPayouts);
    }
    return responseCounts;
}

The first 6 lines of code of each of these methods are totally duplicated, but I'm not sure how I should or can clean this up.

I think an extension of this problem is that I have two chains of method calls which do the same thing for all but the data that is collected, and instead of having a boolean operator to split this functionality as I thought that was bad design, I implemented a new chain of methods to get it done. Should I have done this differently?


Solution

  • You can create one common method and pass type like withCount as below,

    Map<Integer, Integer> getResponses(int baseCostMultiplier, int reels, int visibleSymbols, String stakeCostUuid, int totalBets, boolean withCount) throws InsufficientFundsException {
        final int stake = getStake(baseCostMultiplier, stakeCostUuid);
        long balance = 10L * stake;
        final TestGpasPlatform testGpasPlatform = TestGpasPlatform.create(ryotaAdapter, TestGpasPlatform.DEFAULT_MIN_BET, Math.max(stake, TestGpasPlatform.DEFAULT_MAX_BET), TestGpasPlatform.DEFAULT_MAX_WIN, ImmutableList.of(baseCostMultiplier));
    
        final Map<Integer, Integer> responseCounts = new HashMap<>();
        for (int count = 0; count < totalBets; count++) {
            final Tuple2<List<Output>, TestGpasPlatform> result = playWithRealRng(baseCostMultiplier, count, reels, visibleSymbols, stakeCostUuid, testGpasPlatform);
            // If we run out of balance, re-start, we want to do meaningful spins that trigger features, etc
            balance = checkBalance(stake, balance, result._1(), count);
            final int byteLength = result._2.getLastResponse().map(s -> s.getBytes().length).orElse(0);
    
            if(withCount) {
                responseCounts.putIfAbsent(byteLength, 0);
                responseCounts.put(byteLength, responseCounts.get(byteLength) + 1);
            }else{
                final PlayData playData = result._2.getLastResponse().map(s -> new Gson().fromJson(s, GdkPlayData.class)).orElse(new GdkPlayData());
                final java.util.List<SlotsActionData> actionData = playData.findLastPlay().getLastPlayInModeData().getSlotsData().getActions();
                final int sumOfPayouts = actionData.stream()
                        .map(SlotsActionData::getPayouts)
                        .mapToInt(java.util.List::size)
                        .sum();
                responseCounts.putIfAbsent(byteLength, sumOfPayouts);
            }
        }
        return responseCounts;
    }