본문 바로가기
프로그래밍/디자인 패턴

[Spring & Design Pattern] Spring에서 발견한 Design Pattern_strategy pattern

by 사바라다 2019. 10. 16.

 

안녕하세요. 오늘은 디자인패턴 중 꽃이라고 불리는 strategy pattern에 대해서 알아보도록 하겠습니다.

strategy Pattern

OOP(객체 지향)의 주요 원칙중 하나로 Open-Closed Principle(개방폐쇄원칙)이 있습니다. 간략히 말하면 "소프트웨어의 구성요소들은 확장에는 열려있어야하고, 변경에는 닫혀있어야 한다." 라는 원칙입니다. 즉, 요구사항의 변경이 있을 때 기존 구성요소의 수정은 최소화 하되 확장에는 적극적이어야 한다는 말입니다. 확장에는 기존 구성요소의 변경이 없습니다. 결합도가 낮다는 의미이기도 합니다.

strategy Pattern은 개방폐쇄원칙에 잘 맞아 떨어지는 패턴입니다.

 

출처 : https://sourcemaking.com/design_patterns/strategy

위의 이미지는 strategy 패턴을 보여주는 diagram입니다. 보면 클라이언트는 doSomething이라는 interface를 사용하며, 구현 class를 직접 사용하지 않습니다. 이렇게 함으로써 결합도를 낮출 수 있습니다. 만약 새로운 구현이 필요하다면 우리는 Abstraction라는 Instance를 상속받은 Class를 하나 더 만들어 사용하면 됩니다.(확장)

그렇다면 strategy 패턴을 Java의 예제로 한번 보도록 하겠습니다.

JAVA 예제

 

출처 : https://sourcemaking.com/design_patterns/strategy

위의 예제는 공항까지가는 운송수단을 선택하는 예제입니다.

public interface Strategy {
    void strategy();
}

가장 먼저 interface로 위와 같이 최상위 설계를 진행합니다. 그리고 아래와 같이 실제 구현을 각 전략에 따라서 생성해줍니다.

public class BusStrategy implements Strategy {
    @Override
    public void strategy() {
        System.out.println("버스로 갑니다.");
    }
}
public class CarStrategy implements Strategy{
    @Override
    public void strategy() {
        System.out.println("자동차로 갑니다.");
    }
}
public class TexiStrategy implements Strategy{
    @Override
    public void strategy() {
        System.out.println("택시로 갑니다.");
    }
}

이렇게 구현까지 작성해 보았습니다. 그렇다면 Client는 어떻게 사용하는지 보겠습니다.

public class TransportationToAirPort {

    Strategy strategy;

    public void setStrategy(Strategy strategy){
        this.strategy = strategy;
    }

    public void printHowToGoAirPort(){
        strategy.strategy();
    }

    public static void main(String[] args) {
        Strategy[] strategies = {new BusStrategy(), new CarStrategy(), new TexiStrategy()};

        for (Strategy strategy : strategies) {
            TransportationToAirPort transportationToAirPort = new TransportationToAirPort();
            transportationToAirPort.setStrategy(strategy);
            transportationToAirPort.printHowToGoAirPort();
        }
    }
}

위와같이 사용하는 class와 main class를 작성해 보았습니다. main에서는 전략을 배열로 넣어두고 TransportationToAirPort Class에 주입하였고 주입된 전략에 따라 별도로 출력되게 하였습니다.

이렇게 하면 아래와 같이 결과가 출력됩니다.

버스로 갑니다.
자동차로 갑니다.
택시로 갑니다.

strategy 패턴은 Spring에서 주로 사용되는 대표적인 전략 중 하나입니다. Spring의 어디에서 사용되는지 확인해보겠습니다.

in Spring

Spring Boot는 Spring을 아주 간단하게 사용할 수 있도록 만든 framework입니다.

SpringApplication.run(parameters);

위 명령어 하나면 Spring Boot가 지향하는 정말 컴퓨터 전원을 키듯이 웹서버를 사용할 수 있습니다. 사실 이 run method 안에서는 웹서버를 올리고 beanFactory를 초기화 하고 우리가 Spring에서 수동으로 세팅했던 것들을 자동으로 처리해 줍니다. 아래 코드는 run 내부에 있는 로직입니다.

public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        ... 중략 ...
}

이런 환경세팅 가운데 많은부분이 strategy 패턴에 사용되었습니다. 그중에 하나인 ClassLoader를 따라가 확인해보겠습니다.

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        return new SpringApplication(primarySources).run(args);
}

public SpringApplication(Class<?>... primarySources){
        this(null, primarySources);
}

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {

        // resourceLoader 주입
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        this.webApplicationType = WebApplicationType.deduceFromClasspath();

        // classLoader를 이용한 SpringFactory 취득
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

        this.mainApplicationClass = deduceMainApplicationClass();
    }

위의 메서드들은 SpringFactory instance를 생성하는 메서드입니다. 우리는 위메서드를 통해서 ResourceLoader가 ClassLoader를 불러오는 것을 확인했습니다. ResourceLoader는 아래와 같은 Interface입니다.

/**
 * Strategy interface for loading resources (e.. class path or file system
 * resources). An {@link org.springframework.context.ApplicationContext}
 * is required to provide this functionality, plus extended
 * {@link org.springframework.core.io.support.ResourcePatternResolver} support.
 *
 * <p>{@link DefaultResourceLoader} is a standalone implementation that is
 * usable outside an ApplicationContext, also used by {@link ResourceEditor}.
 *
 * <p>Bean properties of type Resource and Resource array can be populated
 * from Strings when running in an ApplicationContext, using the particular
 * context's resource loading strategy.
 *
 * @author Juergen Hoeller
 * @since 10.03.2004
 * @see Resource
 * @see org.springframework.core.io.support.ResourcePatternResolver
 * @see org.springframework.context.ApplicationContext
 * @see org.springframework.context.ResourceLoaderAware
 */
public interface ResourceLoader {

    /** Pseudo URL prefix for loading from the class path: "classpath:". */
    String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
... 중략 ...

그리고 이 interface의 getClassLoader method를 통해 ClassLoader를 가져옵니다. 실제 구현클래스는 아래와 같습니다.

ResourceLoader의 구현 Class List

우리는 이렇게 해서 이 많은 ClassLoader구현 클래스 중 Spring Boot를 사용할 때 사용자가 1가지 전략을 선택하여 사용할 수 있다는 사실을 알 수 있었습니다.

마무리

오늘은 이렇게 strategy 패턴에 대해서 알아보고 구현 및 Spring에서의 전략을 확인해보았습니다.

궁금하신점은 댓글로 남겨주시면 답변 또는 같이 고민하도록 하겠습니다.

감사합니다.

참조

https://sourcemaking.com/design_patterns/strategy

댓글