스프링 부트에서 전략 패턴을 구현하는 간결한 방법

소프트웨어 설계에서 전략 패턴(Strategy Pattern)은 특정 작업을 수행하는 다양한 알고리즘을 캡슐화하고 런타임에 이들을 교체할 수 있게 하는 유연한 디자인 패턴입니다. 스프링 부트 애플리케이션에서 이 패턴을 구현하는 여러 방법이 있지만, 때로는 복잡한 설정이나 수동적인 빈(Bean) 관리가 필요하여 코드의 복잡성을 증가시키기도 합니다. 이 글에서는 스프링 부트의 강력한 의존성 주입(Dependency Injection) 기능을 활용하여 전략 패턴을 매우 간결하게 구현하는 방법을 소개합니다.

전략 인터페이스 정의

먼저, 모든 전략 구현체가 따를 공통 인터페이스를 정의합니다. 이 인터페이스는 각 전략이 수행할 작업을 추상화합니다. 예를 들어, 특정 작업을 실행하고 결과를 반환하는 StrategyExecutor 인터페이스를 생각해볼 수 있습니다.

public interface StrategyExecutor {
    String execute();
}

구체적인 전략 구현

이제 StrategyExecutor 인터페이스를 구현하는 여러 클래스를 정의합니다. 각 클래스는 특정 전략을 나타내며, 스프링 컨테이너에 빈으로 등록될 수 있도록 @Service 어노테이션을 사용합니다. 이때, @Service 어노테이션에 빈 이름을 지정하는 것이 중요합니다. 이 이름은 나중에 특정 전략을 선택하는 '선택자' 역할을 하게 됩니다.

전략 서비스 A (ConcreteStrategyA)

@Service("StrategyA")
public class ConcreteStrategyA implements StrategyExecutor {
    @Override
    public String execute() {
        return "StrategyA가 실행되었습니다.";
    }
}

전략 서비스 B (ConcreteStrategyB)

@Service("StrategyB")
public class ConcreteStrategyB implements StrategyExecutor {
    @Override
    public String execute() {
        return "StrategyB가 실행되었습니다.";
    }
}

전략 서비스 C (ConcreteStrategyC)

@Service("StrategyC")
public class ConcreteStrategyC implements StrategyExecutor {
    @Override
    public String execute() {
        return "StrategyC가 실행되었습니다.";
    }
}

전략 빈 주입 및 사용

이제 정의된 전략 구현체들을 사용하는 곳에서 스프링의 의존성 주입 기능을 활용하여 이들을 손쉽게 가져올 수 있습니다. 스프링은 특정 인터페이스를 구현하는 모든 빈들을 자동으로 리스트(List)나 맵(Map) 형태로 주입할 수 있는 강력한 기능을 제공합니다.

List<StrategyExecutor> 형태로 주입하면, 해당 인터페이스를 구현하는 모든 빈들이 순서 없이 리스트에 담깁니다. 이는 모든 전략을 순회하며 특정 작업을 수행할 때 유용할 수 있습니다.

더욱 강력한 방법은 Map<String, StrategyExecutor> 형태로 주입하는 것입니다. 이 경우, 맵의 키(key)는 각 빈의 이름(@Service("빈이름")으로 지정한 이름)이 되고, 값(value)은 해당 빈의 인스턴스가 됩니다. 이 방식을 사용하면 런타임에 특정 이름으로 원하는 전략을 즉시 찾아 사용할 수 있습니다.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Map;

@RestController
public class StrategyController {

    // StrategyExecutor 인터페이스를 구현하는 모든 빈이 List로 주입됩니다.
    @Autowired
    private List<StrategyExecutor> strategyList;

    // StrategyExecutor 인터페이스를 구현하는 모든 빈이 빈 이름을 키로 하여 Map으로 주입됩니다.
    @Autowired
    private Map<String, StrategyExecutor> strategyMap;

    @GetMapping("/execute-strategy")
    public String performStrategy(@RequestParam String strategyName) {
        StringBuilder result = new StringBuilder();

        // List를 통해 모든 전략 실행 (예시)
        result.append("모든 전략 목록 (총 ").append(strategyList.size()).append("개):\n");
        for (StrategyExecutor strategy : strategyList) {
            result.append("- ").append(strategy.execute()).append("\n");
        }

        // Map을 통해 특정 이름의 전략 실행
        StrategyExecutor selectedStrategy = strategyMap.get(strategyName);
        if (selectedStrategy != null) {
            result.append("\n선택된 전략 '").append(strategyName).append("' 실행 결과:\n");
            result.append("- ").append(selectedStrategy.execute());
        } else {
            result.append("\n오류: '").append(strategyName).append("' 전략을 찾을 수 없습니다.");
        }
        return result.toString();
    }
}

동작 테스트

위 코드를 실행한 후, 웹 브라우저나 API 클라이언트를 통해 다음 URL로 요청을 보내어 테스트할 수 있습니다.

GET http://localhost:8080/execute-strategy?strategyName=StrategyA

strategyName 파라미터에 "StrategyA", "StrategyB", 또는 "StrategyC"를 전달하여 각기 다른 전략의 실행 결과를 확인할 수 있습니다.

결론

이 방식은 스프링 부트의 자동 의존성 주입 기능을 최대한 활용하여 전략 패턴을 구현합니다. 추가적인 설정 클래스나 복잡한 빈 스캔 로직 없이 매우 간결하게 전략들을 관리하고 사용할 수 있다는 장점이 있습니다. 이는 대부분의 일반적인 전략 패턴 구현 요구사항을 충족시키며 코드의 가독성과 유지보수성을 높여줍니다.

다만, 전략 빈의 초기화 과정에서 매우 복잡하거나 특별한 로직이 필요한 경우에는 이 방식보다 더 커스터마이징된 빈 등록 방식(예: @Configuration 클래스에서 @Bean 메서드를 통한 수동 등록)이 필요할 수 있습니다. 하지만 간결함과 효율성을 중시한다면, @Autowired를 이용한 List 또는 Map 주입 방식은 스프링 부트에서 전략 패턴을 구현하는 가장 우아하고 실용적인 방법 중 하나입니다.

태그: Spring Boot Strategy Pattern Dependency Injection autowired Service Locator

11월 14일 05:33에 게시됨