728x90
반응형
SMALL
SMALL

참고자료

 

스프링 핵심 원리 - 고급편 강의 | 김영한 - 인프런

김영한 | 스프링의 핵심 원리와 고급 기술들을 깊이있게 학습하고, 스프링을 자신있게 사용할 수 있습니다., 핵심 디자인 패턴, 쓰레드 로컬, 스프링 AOP스프링의 3가지 핵심 고급 개념 이해하기

www.inflearn.com

 

전략 패턴 - 시작

전략 패턴은 템플릿 메서드 패턴의 단점을 극복할 수 있는 또다른 디자인 패턴이다. 이 패턴 역시 공통 부분을 한곳에 두고 중복 코드를 제거하고 변경되는 부분만을 유연하게 작성해서 사용하는 패턴인데 어떻게 템플릿 메서드 패턴의 단점을 극복할까?

 

전략 패턴이란 말이 좀 한번에 와닿지 않을 수 있는데 내가 이해한 전략이란건 이 공통 로직을 제외한 변경되는 로직을 처리하는 그 방법을 말한다. 즉, 이 변경되는 로직을 전략이라 말하고 그 전략을 전달받아 실행하는 코드가 있는 것이라고 생각하면 된다. 변하지 않는 부분을 Context라는 곳에 두고, 변하는 부분을 Strategy라는 인터페이스를 만들고 해당 인터페이스를 구현하도록 해서 문제를 해결한다. 상속이 아니라 위임으로 문제를 해결하는 것이다. 전략 패턴에서 Context는 변하지 않는 템플릿 역할을 하고, Strategy는 변하는 알고리즘 역할을 한다. 

 

GOF 디자인 패턴에서 정의한 전략 패턴의 의도는 다음과 같다.

알고리즘 제품군을 정의하고 각각을 캡슐화하여 상호 교환 가능하게 만들자. 전략을 사용하면 알고리즘을 사용하는 클라이언트와 독립적으로 알고리즘을 변경할 수 있다.

 

 

Strategy

package cwchoiit.springadvanced.trace.strategy.code.strategy;

@FunctionalInterface
public interface Strategy {
    void call();
}

 

  • Strategy 라는 함수형 인터페이스를 하나 선언하고 그 인터페이스가 가지는 메서드는 call()이다.

StrategyLogic1

package cwchoiit.springadvanced.trace.strategy.code.strategy;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class StrategyLogic1 implements Strategy {
    @Override
    public void call() {
        log.info("비즈니스 로직1 실행");
    }
}

StrategyLogic2

package cwchoiit.springadvanced.trace.strategy.code.strategy;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class StrategyLogic2 implements Strategy {
    @Override
    public void call() {
        log.info("비즈니스 로직2 실행");
    }
}

 

  • Strategy 인터페이스를 구현하는 구현체 두 개가 각각 다른 로직의 call() 메서드가 있다. 그럼 하나는 전략A, 하나는 전략B가 되는 셈이고 이 전략을 가져다가 사용할 녀석 하나만 만들면 된다. 그리고 그때그때마다 전략을 갈아 끼우는 것 = 전략 패턴이다.

 

전략 패턴 - 필드로 전략을 조립

이제 실제 전략들을 받아서 사용할 클래스를 하나 만들면 되는데 통상적으로 이 클래스를 가지고 Context라고 칭한다. 

ContextV1

package cwchoiit.springadvanced.trace.strategy.code.strategy;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RequiredArgsConstructor
public class ContextV1 {

    private final Strategy strategy;

    public void execute() {
        long startTime = System.currentTimeMillis();
        strategy.call();
        long endTime = System.currentTimeMillis();

        long resultTime = endTime - startTime;
        log.info("resultTime = {}ms", resultTime);
    }
}

 

  • ContextV1 클래스는 전략을 필드로 가지고 있는 방식이다. 즉, 필드로 받기 위해서 전략을 외부에서 주입받게 된다.
  • 그리고 실제 실행 코드는 이 클래스가 가지고 있는 execute() 메서드이다.
  • 변경되는 비즈니스 로직 부분은 전략이 가지고 있는 call() 메서드에 있다.
  • 전략 패턴의 핵심은 ContextStrategy 인터페이스에만 의존한다는 점이다. 덕분에 Strategy의 구현체를 변경하거나 새로 만들어도 Context의 코드에는 영향을 전혀 주지 않는다.

ContextV1Test

package cwchoiit.springadvanced.trace.strategy;

import cwchoiit.springadvanced.trace.strategy.code.strategy.ContextV1;
import cwchoiit.springadvanced.trace.strategy.code.strategy.StrategyLogic1;
import cwchoiit.springadvanced.trace.strategy.code.strategy.StrategyLogic2;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;

@Slf4j
public class ContextV1Test {

    @Test
    void strategyV1() {
        StrategyLogic1 logic1 = new StrategyLogic1();
        ContextV1 contextV1 = new ContextV1(logic1);
        contextV1.execute();

        StrategyLogic2 logic2 = new StrategyLogic2();
        ContextV1 contextV2 = new ContextV1(logic2);
        contextV2.execute();
    }
}
  • 전략 패턴을 사용해서 테스트 해보자. 코드를 보면 의존관계 주입을 통해 ContextV1Strategy의 구현체인 logic1을 주입하는 것을 확인할 수 있다.
  • 이렇게 해서 Context안에 원하는 전략을 주입한다. 이렇게 원하는 모양으로 조립을 완료하고 난 다음에 context1.execute()를 호출해서 context를 실행한다.

 

전략 패턴 - 익명 내부 클래스, 람다 사용

@Test
void strategyV2() {
    Strategy strategyLogic1 = new Strategy() {
        @Override
        public void call() {
            log.info("비즈니스 로직1 실행");
        }
    };
    ContextV1 context1 = new ContextV1(strategyLogic1);
    context1.execute();

    Strategy strategyLogic2 = new Strategy() {
        @Override
        public void call() {
            log.info("비즈니스 로직2 실행");
        }
    };
    ContextV1 context2 = new ContextV1(strategyLogic2);
    context2.execute();
}

@Test
void strategyV3() {
    ContextV1 context1 = new ContextV1(new Strategy() {
        @Override
        public void call() {
            log.info("비즈니스 로직1 실행");
        }
    });
    context1.execute();

    ContextV1 context2 = new ContextV1(new Strategy() {
        @Override
        public void call() {
            log.info("비즈니스 로직2 실행");
        }
    });
    context2.execute();
}

@Test
void strategyV4() {
    ContextV1 context1 = new ContextV1(() -> log.info("비즈니스 로직1 실행"));
    context1.execute();

    ContextV1 context2 = new ContextV1(() -> log.info("비즈니스 로직2 실행"));
    context2.execute();
}
  • 익명 내부 클래스 사용 및 람다를 사용한 코드이다. 훨씬 코드가 깔끔해진 모습이다. 

선조립 후실행

여기서는 Context의 내부 필드에 Strategy를 두고 사용하고 있다. 이 방식은 ContextStrategy를 실행 전에 원하는 모양으로 조립해두고, 그 다음에 Context를 실행하는 선조립, 후실행 방식에서 유용하다. 한번 조립해놓고 이후에 계속해서 재사용이 가능한 형태이다. 그래서 아래와 같은 행위가 가능하다.

@Test
void strategyV4() {
    ContextV1 context1 = new ContextV1(() -> log.info("비즈니스 로직1 실행"));
    context1.execute();

    // 코드 만 줄 ...

    context1.execute();
}

한번만 조립해두면 계속 재사용할 수 있다는 것을 표현했다. 그런데 이 방식의 단점은 ContextStrategy를 조립한 이후에는 전략을 변경하기가 번거롭다는 점이다. 물론 ContextSetter를 제공해서 Strategy를 넘겨 받아 변경하면 되지만, Context를 싱글톤으로 사용할 때는 동시성 이슈 등 고려할 점이 많다. 그래서 전략을 실시간으로 변경해야 하면 차라리 위에서 본 것처럼 Context를 하나 더 생성해서 그곳에 다른 Strategy를 주입하는 것이 더 나은 선택일 수 있다. 그럼 이렇게 먼저 조립하고 사용하는 방식보다 더 유연하게 전략 패턴을 사용하는 방법은 없을까? 

 

전략 패턴 - 전략을 파라미터로 전달

위에서 말한 단점을 다른 방법으로 해결하는 방법이다. 이전에는 Context의 필드에 Strategy를 주입해서 사용했다. 이번에는 전략을 실행할 때 직접 파라미터로 전달해서 사용해보자.

 

ContextV2

package cwchoiit.springadvanced.trace.strategy.code.strategy;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ContextV2 {

    public void execute(Strategy strategy) {
        long startTime = System.currentTimeMillis();
        strategy.call();
        long endTime = System.currentTimeMillis();

        long resultTime = endTime - startTime;
        log.info("resultTime = {}ms", resultTime);
    }
}

 

  • 이젠 Context는 필드에 전략을 보관하지 않는다. 대신 실행 코드에서 파라미터로 전략을 전달받는다. 이렇게 되면 그때 그때 전략이 계속 변경될 경우 한번의 초기화만으로 파라미터에 전략만 바꿔 실행하면 된다. 바로 다음 코드처럼.

ContextV2Test

package cwchoiit.springadvanced.trace.strategy;

import cwchoiit.springadvanced.trace.strategy.code.strategy.ContextV2;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;

@Slf4j
public class ContextV2Test {

    @Test
    void strategy_parameter() {
        ContextV2 ctx = new ContextV2();
        ctx.execute(() -> log.info("비즈니스 로직1 실행"));
        ctx.execute(() -> log.info("비즈니스 로직2 실행"));
    }
}

 

  • Context 객체는 이제 단 한번만 생성하면 된다. 전략의 변경이 있어도 파라미터로 넘겨주면 그만이다. 이러한 장점이 있는 반면, 단점은 같은 전략을 사용한다해도 계속 파라미터로 넘겨줘야 하는 번거로움이 있다. 즉, All-in-One이 아니라 Trade-Off가 있다. 각 상황에 맞게 사용하면 된다.

템플릿

지금 우리가 해결하고 싶은 문제는 변하는 부분과 변하지 않는 부분을 분리하는 것이다. 변하지 않는 부분을 템플릿이라고 하고, 그 템플릿 안에서 변하는 부분에 약간 다른 코드 조각을 넘겨서 실행하는 것이 목적이다. ContextV1, ContextV2 두 가지 방식 다 문제를 해결할 수 있지만 어떤 방식이 조금 더 나아 보이는가? 지금 우리가 원하는 것은 애플리케이션 의존 관계를 설정하는 것처럼 선조립, 후실행이 아니다. 단순히 코드를 실행할 때 변하지 않는 템플릿이 있고 그 템플릿 안에서 원하는 부분만 살짝 다른 코드를 실행하고 싶을 뿐이다. 따라서 우리가 고민하는 문제는 실행 시점에 유연하게 실행 코드 조각을 전달하는 ContextV2가 더 적합하다.

 

 

728x90
반응형
LIST

'Spring Advanced' 카테고리의 다른 글

Proxy/Decorator Pattern 2 (JDK 동적 프록시)  (0) 2023.12.14
Proxy/Decorator Pattern  (0) 2023.12.13
Template Callback Pattern  (0) 2023.12.12
Template Method Pattern  (2) 2023.12.12
ThreadLocal  (0) 2023.12.12
728x90
반응형
SMALL
반응형
SMALL

회사에서 새로운 프로젝트를 진행할 때 이번엔 Maven말고 Gradle을 사용해보고 싶어서 로컬에서는 아무 문제가 없었는데 물리서버에 코드를 옮기고 빌드할 때 이러한 에러가 발생했다.

java.lang.UnsupportedClassVersionError:  
org/springframework/boot/gradle/plugin/SpringBootPlugin 
has been compiled by a more recent version of the Java Runtime (class file version 61.0),
this version of the Java Runtime only recognizes class file versions up to 52.0

이 에러 내용만을 읽어봤을 땐 현재 사용하는 JRE가 클래스 파일의 버전 52까지만 수용할 수 있는듯하고 이 경로 org/springframework/boot/gradle/plugin/SpringBootPlugin의 클래스 파일의 버전은 61이라 버전 미스매치가 된 것 같다.

 

해결하는 방법은 그냥 현재 물리서버에서 사용중인 JRE 버전이 로컬에서 사용한 JRE 버전보다 낮고 허용가능한 범위를 벗어났으니 JRE 버전을 높이면 된다. 아래는 클래스 파일의 버전과 호환 가능한 JRE 버전이다.

49 = Java 5
50 = Java 6
51 = Java 7
52 = Java 8
53 = Java 9
54 = Java 10
55 = Java 11
56 = Java 12
57 = Java 13
58 = Java 14
59 = Java 15
60 = Java 16
61 = Java 17
62 = Java 18
63 = Java 19
64 = Java 20
65 = Java 21

 

 

728x90
반응형
LIST

+ Recent posts