Backend Development 11 min read

Refactor Java Business Logic with Strategy, Factory, and Template Method Patterns

This article demonstrates step‑by‑step how to improve existing Java code by applying the Strategy, Factory, and Template Method design patterns, reducing duplicated if‑else branches, adhering to the Open‑Closed and Single‑Responsibility principles, and making the logic more extensible and maintainable.

macrozheng
macrozheng
macrozheng
Refactor Java Business Logic with Strategy, Factory, and Template Method Patterns

Preface

In this article we show how to gradually refactor existing code using design patterns, focusing on Strategy, Factory, and Template Method, which are the most frequently used patterns in daily work.

1. Original Pseudocode Flow

Original code (Java) that decides market hit based on type, switch flags, and request number, with duplicated if‑else branches for enterprise and market_list.

<code>class Parameter{
    int pageSize;
    int pageNo;
    int reqNum;
    // other parameters.
}

// Logic to determine market hit
boolean isMarketHit(Parameter dto){
    // Enterprise type
    if(dto.type == 'enterprise'){
        // Switch closed, no request
        if(isEnterpriseSwitchClose){
            return false;
        }
        // Single record
        if(dto.reqNum==1){
            return singleRemoteEOIinvoke(dto);
        }else if(dto.reqNum>1){
            return batchRemoteEOIinvoke(dto);
        }
    }
    // Market list type
    else if(dto.type=='market_list'){
        if(isMarketListSwitchClose){
            return false;
        }
        if(dto.reqNum==1){
            return singleRemoteMarketinvoke(dto);
        }else if(dto.reqNum>1){
            return batchRemoteMarketinvoke(dto);
        }
    }
}
</code>

The problems are that the if‑branches become bloated and adding a new type requires modifying the original class, violating the Open‑Closed Principle and Single‑Responsibility Principle.

If the number of

if

branches grows, the code becomes bulky.

Adding a new type forces changes in the source code.

2. Applying Strategy Pattern

When multiple if…else branches can be encapsulated and swapped, the Strategy pattern can be used.

Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. An interface

IGroupLabelStrategyService

with

processBiz(Parameter dto)

and

getType()

is introduced, and two concrete strategies – Enterprise and MarketList – implement the interface.

<code>// Interface
interface IGroupLabelStrategyService {
    boolean processBiz(Parameter dto);
    String getType();
}

// Enterprise strategy
class EnterpriseGroupLablelStrategyServiceImpl implements IGroupLabelStrategyService {
    public boolean processBiz(Parameter dto){
        if(isEnterpriseSwitchClose){
            return false;
        }
        if(dto.reqNum==1){
            return singleRemoteEOIinvoke(dto);
        }else if(dto.reqNum>1){
            return batchRemoteEOIinvoke(dto);
        }
        return false;
    }
    public String getType(){
        return "enterprise";
    }
}

// Market list strategy
class MarketListGroupLablelStrategyServiceImpl implements IGroupLabelStrategyService {
    public boolean processBiz(Parameter dto){
        if(isMarketListSwitchClose){
            return false;
        }
        if(dto.reqNum==1){
            return singleRemoteMarketinvoke(dto);
        }else if(dto.reqNum>1){
            return batchRemoteMarketinvoke(dto);
        }
        return false;
    }
    public String getType(){
        return "market_list";
    }
}
</code>

3. Using Factory Pattern

Strategy implementations are injected into a map via

ApplicationContextAware

, which acts as a simple factory to obtain the correct strategy based on

dto.getType()

.

<code>@Component
public class GroupLabelStrategyServiceFactory implements ApplicationContextAware {
    private Map<String, IGroupLabelStrategyService> map = new ConcurrentHashMap<>();

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, IGroupLabelStrategyService> tempMap = applicationContext.getBeansOfType(IGroupLabelStrategyService.class);
        tempMap.values().forEach(strategyService -> map.put(strategyService.getType(), strategyService));
    }

    public boolean processBiz(ParamDTO dto){
        IGroupLabelStrategyService groupLabelStrategyService = map.get(dto.getType());
        if(groupLabelStrategyService != null){
            return groupLabelStrategyService.processBiz(dto);
        }
        return false;
    }
}
</code>

The original business method now delegates to the factory, reducing the method to a single call.

<code>boolean isMarketHit(Parameter dto){
    return groupLabelStrategyServiceFactory.processBiz(dto);
}
</code>

4. Applying Template Method Pattern

Define the skeleton of an algorithm while allowing subclasses to override specific steps.

Both strategies share the same sequence: switch check → single‑record call → batch call. This common flow is extracted into an abstract class

AbstractGroupLabelJudgeTemplate

that implements the template method.

<code>public abstract class AbstractGroupLabelJudgeTemplate implements IGroupLabelStrategyService {
    // Template method
    public boolean processBiz(Parameter dto){
        if(isSwitchClose()){
            return false;
        }
        if(dto.reqNum==1){
            return singleRemote(dto);
        }else if(dto.reqNum>1){
            return batchRemote(dto);
        }
        return false;
    }
    // Steps to be provided by subclasses
    protected abstract boolean isSwitchClose();
    protected abstract boolean singleRemote(Parameter dto);
    protected abstract boolean batchRemote(Parameter dto);
}
</code>

Concrete strategy classes extend the template and provide implementations for the abstract steps.

<code>class EnterpriseGroupLablelStrategyServiceImpl extends AbstractGroupLabelJudgeTemplate {
    protected boolean isSwitchClose(){
        // enterprise switch logic
    }
    protected boolean singleRemote(Parameter dto){
        return singleRemoteEOIinvoke(dto);
    }
    protected boolean batchRemote(Parameter dto){
        return batchRemoteEOIinvoke(dto);
    }
    public String getType(){
        return "enterprise";
    }
}

class MarketListGroupLablelStrategyServiceImpl extends AbstractGroupLabelJudgeTemplate {
    protected boolean isSwitchClose(){
        // market list switch logic
    }
    protected boolean singleRemote(Parameter dto){
        return singleRemoteMarketinvoke(dto);
    }
    protected boolean batchRemote(Parameter dto){
        return batchRemoteMarketinvoke(dto);
    }
    public String getType(){
        return "market_list";
    }
}
</code>

5. Final Thoughts

Strategy, Factory, and Template Method patterns are among the most used in everyday development; extracting them as shown can make the codebase more extensible and maintainable.

Design PatternsJavastrategy patternbackend developmentFactory 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.