Backend Development 13 min read

Mastering Code Refactoring: From Repetition to Design Patterns in Java

This article walks through a step‑by‑step refactoring of repetitive Java code, illustrating how to extract common methods, use reflection, apply generics with lambda expressions, leverage inheritance and the template method pattern, and finally combine factory, template, and strategy patterns into a clean, reusable solution.

macrozheng
macrozheng
macrozheng
Mastering Code Refactoring: From Repetition to Design Patterns in Java

1. Example Before Optimization

The initial scenario simulates a reconciliation task that reads files from two endpoints (A and B), converts them into lists, transforms the lists into maps using a unique key, and then compares each field of the resulting maps.

<code>private void checkDetail(String detailPathOfA,String detailPathOfB) throws IOException {
    // Read A side file
    List<DetailDTO> resultListOfA = new ArrayList<>();
    try (BufferedReader reader1 = new BufferedReader(new FileReader(detailPathOfA))) {
        String line;
        while ((line = reader1.readLine()) != null) {
            resultListOfA.add(DetailDTO.convert(line));
        }
    }
    // Read B side file
    List<DetailDTO> resultListOfB = new ArrayList<>();
    try (BufferedReader reader1 = new BufferedReader(new FileReader(detailPathOfB))) {
        String line;
        while ((line = reader1.readLine()) != null) {
            resultListOfB.add(DetailDTO.convert(line));
        }
    }
    // Convert A list to map
    Map<String,DetailDTO> resultMapOfA = new HashMap<>();
    for(DetailDTO detail:resultListOfA){
        resultMapOfA.put(detail.getBizSeq(),detail);
    }
    // Convert B list to map
    Map<String,DetailDTO> resultMapOfB = new HashMap<>();
    for(DetailDTO detail:resultListOfB){
        resultMapOfB.put(detail.getBizSeq(),detail);
    }
    // Compare each entry
    for (Map.Entry<String, DetailDTO> temp : resultMapOfA.entrySet()) {
        if (resultMapOfB.containsKey(temp.getKey())) {
            DetailDTO detailOfA = temp.getValue();
            DetailDTO detailOfB = resultMapOfB.get(temp.getKey());
            if (!detailOfA.getAmt().equals(detailOfB.getAmt())) {
                log.warn("amt is different,key:{}", temp.getKey());
            }
            if (!detailOfA.getDate().equals(detailOfB.getDate())) {
                log.warn("date is different,key:{}", temp.getKey());
            }
            if (!detailOfA.getStatus().equals(detailOfB.getStatus())) {
                log.warn("status is different,key:{}", temp.getKey());
            }
            // ... other field checks
        }
    }
}
</code>

2. Extract Common Method to Remove Duplication

Identify duplicated code such as file reading and list‑to‑map conversion, and extract them into reusable methods like

readFile

and

convertListToMap

.

3. Use Reflection for Field Comparison

When objects have many fields, reflection can automate the field‑by‑field comparison, reducing boilerplate code.

4. Combine Lambda Expressions and Generics

Abstract the file‑reading logic with a generic method that accepts a lambda to convert each line, enabling reuse for both detail and balance files.

<code>private void checkDetail(String detailPathOfA, String detailPathOfB) throws IOException {
    List<DetailDTO> resultListOfA = readDataFromFile(detailPathOfA, DetailDTO::convert);
    List<DetailDTO> resultListOfB = readDataFromFile(detailPathOfB, DetailDTO::convert);
    Map<String, DetailDTO> resultMapOfA = convertListToMap(resultListOfA);
    Map<String, DetailDTO> resultMapOfB = convertListToMap(resultListOfB);
    compareDifferent(resultMapOfA, resultMapOfB);
}

private void checkBalance(String balancePathOfA, String balancePathOfB) throws IOException {
    List<BalanceDTO> resultListOfA = readDataFromFile(balancePathOfA, BalanceDTO::convert);
    List<BalanceDTO> resultListOfB = readDataFromFile(balancePathOfB, BalanceDTO::convert);
    Map<String, BalanceDTO> resultMapOfA = convertListToMap(resultListOfA);
    Map<String, BalanceDTO> resultMapOfB = convertListToMap(resultListOfB);
    compareDifferent(resultMapOfA, resultMapOfB);
}

private <T> void compareDifferent(Map<String, T> mapA, Map<String, T> mapB) {
    for (Map.Entry<String, T> temp : mapA.entrySet()) {
        if (mapB.containsKey(temp.getKey())) {
            T dtoA = temp.getValue();
            T dtoB = mapB.get(temp.getKey());
            List<String> resultList = compareObjects(dtoA, dtoB);
            for (String diff : resultList) {
                log.warn("{} is different,key:{}", diff, ((BaseDTO)dtoA).getKey());
            }
        }
    }
}
</code>

5. Apply Inheritance and Polymorphism

Define an abstract class

BaseKeyDTO

with an abstract

getKey

method. Let both

BalanceDTO

and

DetailDTO

extend it, enabling generic handling of different DTO types.

6. Template Method Pattern

Both detail and balance comparison share the same algorithm skeleton: read files, convert to lists, transform to maps, and compare fields. The template method abstracts this skeleton, allowing subclasses to provide specific conversion logic.

6.1 Define the Comparison Template

Declare an abstract class with a

readDataFromFile

method that returns a

Pair

of lists, and an abstract

convertLineToDTD

for subclasses to implement.

7. Combine Factory, Template Method, and Strategy Patterns

Create an

ICheckStrategy

interface and implement concrete strategies for detail and balance checks. Use a Spring factory to map strategy enums to implementations, allowing callers to select the appropriate check at runtime.

<code>/** Detail check strategy */
@Service
public class CheckDetailStrategyServiceImpl extends AbstractCheckTemplate<DetailDTO> {
    @Override
    protected DetailDTO convertLineToDTD(String line) {
        return DetailDTO.convert(line);
    }
    @Override
    public void check(String filePathA, String filePathB) throws IOException {
        checkTemplate(filePathA, filePathB);
    }
    @Override
    public CheckEnum getCheckEnum() {
        return CheckEnum.DETAIL_CHECK;
    }
}

/** Balance check strategy */
@Service
public class CheckBalanceStrategyServiceImpl extends AbstractCheckTemplate<BalanceDTO> {
    @Override
    protected BalanceDTO convertLineToDTD(String line) {
        return BalanceDTO.convert(line);
    }
    @Override
    public void check(String filePathA, String filePathB) throws IOException {
        checkTemplate(filePathA, filePathB);
    }
    @Override
    public CheckEnum getCheckEnum() {
        return CheckEnum.BALANCE_CHECK;
    }
}
</code>

By leveraging Spring's

ApplicationContextAware

, the strategies are registered in a map, and a unified

checkCompare

method dispatches the appropriate strategy, demonstrating the practical use of factory, template, and strategy patterns together.

design patternsJavaStrategy PatternSpringBootrefactoringFactory Patterntemplate method
macrozheng
Written by

macrozheng

Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.