[Spring] 예제둜 μ•Œμ•„λ³΄λŠ” [ ν…œν”Œλ¦Ώ λ©”μ„œλ“œ νŒ¨ν„΄ ]

2022. 5. 31. 11:08ㆍBackend/🌿 Spring

ν…œν”Œλ¦Ώ λ©”μ„œλ“œ νŒ¨ν„΄ - 사전적 μ •μ˜ / λͺ©μ 

"μž‘μ—…μ—μ„œ μ•Œκ³ λ¦¬μ¦˜μ˜ 골격을 μ •μ˜ν•˜κ³ , 일뢀 단계λ₯Ό ν•˜μœ„ 클래슀둜 μ—°κΈ°ν•œλ‹€. ν…œν”Œλ¦Ώ λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜λ©΄ ν•˜μœ„ ν΄λž˜μŠ€κ°€ μ•Œκ³ λ¦¬μ¦˜μ˜ ꡬ쑰λ₯Ό λ³€κ²½ν•˜μ§€ μ•Šκ³ λ„, μ•Œκ³ λ¦¬μ¦˜μ˜ νŠΉμ • 단계λ₯Ό μž¬μ •μ˜ν•  수 μžˆλ‹€. [GOF] " 

이게 무슨 μ†Œλ¦¬μΈκ°€~ μ•Œμ•„λ³΄κΈ° μœ„ν•΄ μŠ€ν”„λ§ 예제 μ½”λ“œλ‘œ ν…œν”Œλ¦Ώ λ©”μ„œλ“œ νŒ¨ν„΄μ„ μ•Œμ•„λ³΄κ² λ‹€.

 

 

 

μ€‘λ³΅λ˜λŠ” 둜직의 반볡

μ•„λž˜ μ½”λ“œλŠ” 컨트둀러의 request λ©”μ„œλ“œμ— trace λΌλŠ” 둜그λ₯Ό μ‚½μž…ν•œ μ½”λ“œλ‹€. 

핡심 λ‘œμ§μ€ orderService.orderItem(itemId) 와 return "ok" 뢀뢄이 전뢀인데, 둜그 νŠΈλž˜ν‚Ήμ„ ν•˜λ©΄μ„œ 뢀가적인 try ~ catch ꡬ문이 λΆ™μ–΄ μ½”λ“œκ°€ 비ꡐ적 지저뢄해 보인닀.

@RestController
@RequiredArgsConstructor
public class OrderControllerV3 {

    @GetMapping("/v3/request")
    public String request(String itemId) {
        TraceStatus status = null;
        try {
            status = trace.begin("OrderControllerV3.request()");
            orderService.orderItem(itemId);
            trace.end(status);
            return "ok";
        } catch (Exception e) {
            trace.exception(status, e);
            throw e; // μ˜ˆμ™Έλ₯΄ κΌ­ λ‹€μ‹œ λ˜μ Έμ£Όμ–΄μ•Ό ν•œλ‹€.
        }
    }
}

λ§Œμ•½ ν”„λ‘œμ νŠΈ 전체 μ½”λ“œμ— 둜그 νŠΈλž˜ν‚Ήμ„ ν•΄μ•Όν•œλ‹€κ³  κ°€μ •ν•˜μž. λͺ¨λ“  μ»¨νŠΈλ‘€λŸ¬μ™€ μ„œλΉ„μŠ€ λ ˆμ΄μ–΄, λ ˆν¬μ§€ν† λ¦¬ λ ˆμ΄μ–΄μ˜ λ©”μ„œλ“œμ— 둜그 νŠΈλž˜ν‚Ήμ„ μœ„ν•΄, μœ„μ²˜λŸΌ try ~ catch ꡬ문을 달아야 ν•˜λŠ” 것은 κ°œλ°œμžμŠ€λŸ½μ§€ λͺ»ν•œ μ²˜μ‚¬λ‹€. 

 

베슀트 ν”„λž™ν‹°μŠ€λŠ” λ¬Όλ‘  AOP λ₯Ό μ‚¬μš©ν•œ 둜그 트래컀 μ‚½μž…μ΄κ² μ§€λ§Œ, μš°μ„  ν…œν”Œλ¦Ώ λ©”μ„œλ“œ νŒ¨ν„΄μ„ λ„μž…ν•˜μ—¬ μœ„ μ½”λ“œλ₯Ό μ–΄λ–»κ²Œ λ³€κ²½μ‹œν‚¬ 수 μžˆλŠ”μ§€ λ¨Όμ € μ•Œμ•„λ³΄μž. κ·Έ ν›„μ—” μ°¨λ‘€λŒ€λ‘œ μ „λž΅ νŒ¨ν„΄κ³Ό ν…œν”Œλ¦Ώ 콜백 νŒ¨ν„΄μ„ μ μš©ν•˜λ©΄μ„œ 더 λ‚˜μ€ μ„€κ³„λ‘œ λ³€κ²½ μ‹œμΌœλ³΄κ² λ‹€.

 

 

λ³€ν•˜λŠ” 것과 λ³€ν•˜μ§€ μ•ŠλŠ” 것을 뢄리 

쒋은 μ„€κ³„λŠ” λ³€ν•˜λŠ” 것과 λ³€ν•˜μ§€ μ•ŠλŠ” 것을 λΆ„λ¦¬ν•˜λŠ” 것이닀. μœ„ μ½”λ“œμ—μ„œ λ³€ν•˜λŠ” 뢀뢄은 ν•΅μ‹¬λ‘œμ§μΈ service λ ˆμ΄μ–΄λ₯Ό ν˜ΈμΆœν•˜λŠ” 뢀뢄이고, λ³€ν•˜μ§€ μ•ŠλŠ” 뢀뢄은 try ~ catch ꡬ문과 trace.begin() 둜그 νŠΈλž˜ν‚Ή 뢀뢄이닀. λ³€ν•˜μ§€ μ•ŠλŠ” 뢀뢄을 ν…œν”Œλ¦Ών™” ν•˜μ—¬ 곡톡 μš”μ†Œλ‘œ λΊ„ 수 μžˆλ‹€λ©΄, κ·Έλ¦¬ν•˜μ—¬ ν•΄λ‹Ή ν…œν”Œλ¦Ώμ„ ν•„μš”ν•  λ•Œλ§Œ ν˜ΈμΆœν•˜λŠ” 방법을 μƒκ°ν•΄λ³΄μž. 

 

 

ν…œν”Œλ¦Ώ λ©”μ„œλ“œ νŒ¨ν„΄ - 예제

ν…œν”Œλ¦Ώ λ©”μ„œλ“œ νŒ¨ν„΄μ€ 이름 κ·ΈλŒ€λ‘œ ν…œν”Œλ¦Ώμ„ λ§Œλ“€μ–΄ μ‚¬μš©ν•˜λŠ” λ°©μ‹μœΌλ‘œ, ν…œν”Œλ¦Ώμ΄λΌλŠ” 틀에 μœ„μ—μ„œ μ–ΈκΈ‰ν•œ λ³€ν•˜μ§€ μ•ŠλŠ” 뢀뢄을 λͺ°μ•„λ‘”λ‹€. 그리고 λ³€ν•˜λŠ” 뢀뢄인, 핡심 λ‘œμ§μ€ λ³„λ„λ‘œ ν˜ΈμΆœν•˜λ„λ‘ 섀계 ν•΄λ³΄μž. μ•„λž˜ μ½”λ“œμ—μ„œλŠ” λ³€ν•˜μ§€ μ•ŠλŠ” 뢀뢄을 execute() λ©”μ„œλ“œμ— κ΅¬ν˜„ν–ˆκ³ , λ³€ν•˜λŠ” 뢀뢄은 λ³„λ„λ‘œ ν˜ΈμΆœν•  수 μžˆλ„λ‘ (아직 μ •μ˜λ˜μ§€ μ•Šμ€) 좔상 λ©”μ„œλ“œλ‘œ μ„ μ–Έν•΄λ‘μ—ˆλ‹€. 이λ₯Ό κ΅¬μ„±ν•˜λŠ” ν΄λž˜μŠ€κ°€ ν…œν”Œλ¦Ώμ΄ λ˜λŠ” 것이닀.

// AbstractTemplate.class
@Slf4j
public abstract class AbstractTemplate {

    public void execute() {
        long startTime = System.currentTimeMillis();
        
        // λΉ„μ¦ˆλ‹ˆμŠ€ 둜직 μ‹€ν–‰
        call(); // 상속
        // λΉ„μ¦ˆλ‹ˆμŠ€ 둜직 μ’…λ£Œ
        
        long endTime = System.currentTimeMillis();
        long resultTime = endTime - startTime;
        log.info("resultTime = {}", resultTime);
    }

    // 좔상 λ©”μ„œλ“œ
    protected abstract void call();
}

그리고 λ³€ν•˜λŠ” 뢀뢄인 (λΉ„μ¦ˆλ‹ˆμŠ€ 둜직1, 2λ₯Ό μ²˜λ¦¬ν•˜λŠ”) μžμ‹ 클래슀λ₯Ό SubClassLogic1, SubClassLogic2 μœΌλ‘œ μ •μ˜ν•œλ‹€. 이 λ•Œ ν…œν”Œλ¦Ώμ˜ μΆ”μƒλ©”μ„œλ“œμΈ call() λ©”μ„œλ“œλ₯Ό μ˜€λ²„λΌμ΄λ”© ν•œλ‹€. κ·Έλ¦¬ν•˜μ—¬ μ˜€λ²„λΌμ΄λ”© 된 λ©”μ„œλ“œκ°€ ν˜ΈμΆœλœλ‹€.

// SubClassLogic1.class
@Slf4j
public class SubClassLogic1 extends AbstractTemplate {

    @Override
    protected void call() {
        log.info("λΉ„μ¦ˆλ‹ˆμŠ€ 둜직 1 μ‹€ν–‰");
    }
}


// SubClassLogic2.class
@Slf4j
public class SubClassLogic2 extends AbstractTemplate {

    @Override
    protected void call() {
        log.info("λΉ„μ¦ˆλ‹ˆμŠ€ 둜직 2 μ‹€ν–‰");
    }
}

SubClassLogic1, 2 ν΄λž˜μŠ€κ°€ AbstractTemplate μ΄λΌλŠ” 좔상 클래슀λ₯Ό 상속 λ°›λŠ” 것을 λ³Ό 수 μžˆλ‹€.

μžμ‹ 클래슀인 SubClassLogic1, 2 λŠ” μžμ—°μŠ€λŸ½κ²Œ μΆ”μƒλ©”μ„œλ“œμΈ call() λ©”μ„œλ“œλ₯Ό μ˜€λ²„λΌμ΄λ“œ ν•˜κ³ ,

이 λ•Œ μžμ‹ λ§Œμ˜ 핡심 둜직(λ³€ν•˜λŠ” λΆ€λΆ„)을 call() λ©”μ„œλ“œμ— κ΅¬ν˜„ν•œλ‹€.

 

λ‹€ν˜•μ„±μ΄ μ μš©λ˜λ©΄μ„œ, μžμ‹ 클래슀인 SubClassLogic1, 2 의 μΈμŠ€ν„΄μŠ€λŠ” AbstractTemplate 의 νƒ€μž…μ„ μ§€λ‹ˆλ©° λ™μ‹œμ— execute() λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•  수 μžˆλ‹€. execute() λ©”μ„œλ“œκ°€ ν˜ΈμΆœλ˜λ©΄μ„œ κ·Έ 내뢀에 μ„ μ–Έλœ call() λ©”μ„œλ“œκ°€ μ‹€ν–‰λ˜μ–΄, μžμ‹ ν΄λž˜μŠ€μ—μ„œ μ˜€λ²„λΌμ΄λ“œ ν•΄ λ‘” μ½”λ“œκ°€ μ‹€ν–‰λœλ‹€.

@Test
void templateMethodV1() {
    AbstractTemplate template1 = new SubClassLogic1();
    template1.execute();

    AbstractTemplate template2 = new SubClassLogic2();
    template2.execute();
}

κ²°κ΅­ ν…œν”Œλ¦Ώ λ©”μ„œλ“œ νŒ¨ν„΄μ€, λΆ€λͺ¨ ν΄λž˜μŠ€μ— μ•Œκ³ λ¦¬μ¦˜μ˜ 골격인 ν…œν”Œλ¦Ώμ„ μ •μ˜ν•˜κ³ , 일뢀 λ³€κ²½λ˜λŠ” λ‘œμ§μ€ μžμ‹ ν΄λž˜μŠ€μ— λ³„λ„λ‘œ μ •μ˜ν•˜λŠ” 것이닀. κ·Έλ¦¬ν•˜μ—¬ μžμ‹ ν΄λž˜μŠ€κ°€ μ•Œκ³ λ¦¬μ¦˜ μ „μ²΄μ˜ 골격을 λ³€κ²½ν•˜μ§€ μ•Šκ³ , νŠΉμ • λΆ€λΆ„λ§Œ μž¬μ •μ˜ ν•  수 μžˆλ‹€. 즉 상속과 μ˜€λ²„λΌμ΄λ”©μ„ ν†΅ν•œ λ‹€ν˜•μ„±μ„ ν™œμš©ν•˜μ—¬ 문제λ₯Ό ν•΄κ²°ν•˜λŠ” νŒ¨ν„΄μΈ 것이닀.

 

 

읡λͺ…ν΄λž˜μŠ€ / λžŒλ‹€μ‹μ„ μ΄μš©ν•œ ν…œν”Œλ¦Ώ λ©”μ„œλ“œ νŒ¨ν„΄

ν…œν”Œλ¦Ώ λ©”μ„œλ“œ νŒ¨ν„΄μ„ μ μš©ν•˜μ—¬ ν•΅μ‹¬λ‘œμ§κ³Ό λΆ€κ°€λ‘œμ§μ„ λΆ„λ¦¬ν•΄λƒˆμ§€λ§Œ, μœ„ μ½”λ“œλŠ” 맀번 SubClassLogic 을 생성 ν•΄μ•Όν•˜λŠ” λΆˆνŽΈν•¨μ΄ μžˆλ‹€. 이 점은 읡λͺ… ν΄λž˜μŠ€λ‚˜ λžŒλ‹€μ‹μ„ μ΄μš©ν•΄ κ°„μ†Œν™” ν•  수 μžˆλ‹€.

@Test
void templateMethodV2() {

    // 읡λͺ… 클래슀 μ‚¬μš©
    AbstractTemplate template = new AbstractTemplate() {
        @Override
        protected void call() {
            log.info("λΉ„μ¦ˆλ‹ˆμŠ€ 둜직1 μ‹€ν–‰");
        }
    };
    log.info("ν΄λž˜μŠ€μ΄λ¦„1 = {}", template.getClass());
    template.execute();
	
    // λžŒλ‹€μ‹ μ‚¬μš©
    AbstractTemplate template2 = { log.info("λΉ„μ¦ˆλ‹ˆμŠ€ 둜직2 μ‹€ν–‰"); };
    log.info("ν΄λž˜μŠ€μ΄λ¦„2 = {}", template2.getClass());
    template2.execute();
    
    // λ³΅μž‘ν•΄λ³΄μ΄μ§€λ§Œ, λ³„λ„μ˜ SubClassLogic1 같은 λ³„λ„μ˜νŒŒμΌμ„ λ§Œλ“€μ§€ μ•Šμ•„λ„ λœλ‹€.
}

 

 

 

ν…œν”Œλ¦Ώ λ©”μ„œλ“œ νŒ¨ν„΄μ„ 적용

이제 κΈ°μ‘΄ μ½”λ“œμ— ν…œν”Œλ¦Ώ λ©”μ„œλ“œ νŒ¨ν„΄μ„ μ μš©ν•΄λ³΄μž. μš°μ„  ν…œν”Œλ¦Ώμ΄ 될 AbstractTemplate 좔상 클래슀λ₯Ό μƒμ„±ν•œλ‹€. ν…œν”Œλ¦Ώμ—λŠ” μš°λ¦¬κ°€ λΆ€κ°€ 둜직이라 μ—¬κΈ΄, λ³€ν•˜μ§€ μ•ŠλŠ” 뢀뢄에 ν•΄λ‹Ήλ˜λŠ” try ~ catch μ½”λ“œλ₯Ό μ‚½μž…ν•œλ‹€. 그리고 κ·Έ 내뢀에 call() 좔상 λ©”μ„œλ“œλ₯Ό μ„ μ–Έν•˜μ—¬, μžμ‹ ν΄λž˜μŠ€μ—μ„œ μ˜€λ²„λΌμ΄λ“œ 된 λΉ„μ¦ˆλ‹ˆμŠ€λ‘œμ§μ΄ μ‹€ν–‰λ˜λ„λ‘ ν•˜μž. 

// λΆ€λͺ¨ μ—­ν•  (= ν…œν”Œλ¦Ώ)
public abstract class AbstractTemplate<T> {

    private final LogTrace trace;

    public AbstractTemplate(LogTrace trace) {
        this.trace = trace;
    }

    // type 생성 μ‹œμ μ„ 객체 μƒμ„±μ‹œμ μœΌλ‘œ 미룬닀.
    public T execute(String message){
        TraceStatus status = null;
        try {
            status = trace.begin(message);

            // logic 호좜
            T result = call();

            trace.end(status);
            return result;
        } catch (Exception e) {
            trace.exception(status, e);
            throw e;
        }
    }

    protected abstract T call();
}


@RestController
@RequiredArgsConstructor
public class OrderControllerV4 {
    private final OrderServiceV4 orderService;
    private final LogTrace trace;
    @GetMapping("/v4/request")
    public String request(String itemId) {
    
        // 여기에 logκ΄€λ ¨ μ½”λ“œλ₯Ό λͺ¨λ‘ λͺ°μ•„λ„£κ³ , μΆ”μƒλ©”μ„œλ“œ κ΅¬ν˜„λΆ€μ— ν•΅μ‹¬λ‘œμ§μ„ λ„£λŠ”λ‹€
        AbstractTemplate<String> template = new AbstractTemplate<>(trace) {
            @Override
            protected String call() {
                orderService.orderItem(itemId);
                return "ok";
            } };
        return template.execute("OrderController.request()");
    }
}

 

μ–΄λ–€κ°€? λ‹¨μˆœνžˆ try ~ catch ꡬ문만 extract ν•œ 것이 μ•„λ‹ˆλΌ, 둜그λ₯Ό λ‚¨κΈ°λŠ” 뢀뢄에 단일 μ±…μž„ 원칙(SRP)을 지킨 μ½”λ“œλ₯Ό μ§°λ‹€. λ‘œκ·Έμ™€ κ΄€λ ¨λœ λ³€κ²½ 지점을 ν•˜λ‚˜λ‘œ λͺ¨μ•„μ„œ 변경에 μ‰½κ²Œ λŒ€μ²˜ν•  수 μžˆλŠ” ꡬ쑰λ₯Ό μ„€κ³„ν•œ 것이닀 < template method pattern 의 μž₯점 > . 이제 둜그λ₯Ό λ‚¨κΈ°λŠ” λ‘œμ§μ„ λ³€κ²½ν•΄μ•Ό ν•˜λ©΄, λͺ¨λ“  λ©”μ„œλ“œμ— ν©λΏŒλ €μ§„ μ½”λ“œλ₯Ό 일일이 μˆ˜μ •ν•˜μ§€ μ•Šκ³  ν…œν”Œλ¦Ώ μ½”λ“œλ§Œ μˆ˜μ •ν•˜λ©΄ λœλ‹€.

 

ν…œν”Œλ¦Ώ λ©”μ„œλ“œμ˜ ν•œκ³„

μ•žμ„œ μ„€λͺ…ν•œλŒ€λ‘œ, ν…œν”Œλ¦Ώ λ©”μ„œλ“œ νŒ¨ν„΄μ€ 상속과 μ˜€λ²„λΌμ΄λ”©, λ‹€ν˜•μ„±μ˜ νŠΉμ§•μ„ μ΄μš©ν•˜μ—¬ 문제λ₯Ό ν•΄κ²°ν•œλ‹€. ν•˜μ§€λ§Œ μƒμ†λ³΄λ‹€λŠ” μ»΄ν¬μ§€μ…˜μ„ ν™œμš©ν•˜λΌλŠ” 말이 μžˆλ“―μ΄, μƒμ†μœΌλ‘œ 인해 μžμ‹ ν΄λž˜μŠ€κ°€ λΆ€λͺ¨ ν΄λž˜μŠ€μ™€ 컴파일 μ‹œμ μ— κ°•ν•˜κ²Œ κ²°ν•©λ˜λŠ” λ¬Έμ œκ°€ μ‘΄μž¬ν•œλ‹€. μžμ‹ 클래슀의 μž…μž₯μ—μ„œλŠ” call() μ΄λΌλŠ” μΆ”μƒλ©”μ„œλ“œλ§Œ μ˜€λ²„λΌμ΄λ”© ν•  뿐 λΆ€λͺ¨ 클래슀의 κΈ°λŠ₯을 μ „ν˜€ μ‚¬μš©ν•˜μ§€ μ•Šμ€ 채 μƒμ†λ§Œ λ°›κ³  μžˆλŠ” 상황이닀. 

 

상속을 λ°›λŠ” 것은, μžμ‹ ν΄λž˜μŠ€κ°€ λΆ€λͺ¨ 클래슀λ₯Ό μ˜μ‘΄ν•œλ‹€λŠ” 의미이며, μžμ‹ 클래슀의 μ½”λ“œμ— λΆ€λͺ¨ 클래슀의 μ½”λ“œκ°€ λͺ…ν™•ν•˜κ²Œ μ ν˜€μžˆλ‹€λŠ” μ˜λ―Έμ΄κΈ°λ„ ν•˜λ‹€. 이게 μ™œ 쒋지 λͺ»ν•œ μƒν™©μΌκΉŒ? 의쑴의 λŒ€μƒμ΄ λ˜λŠ” ν΄λž˜μŠ€κ°€ λ³€κ²½λ˜λ©΄ μ˜μ‘΄ν•˜λŠ” ν΄λž˜μŠ€λ„ λ³€κ²½μ˜ 영ν–₯을 λ°›κΈ° λ•Œλ¬Έμ΄λ‹€.

 

κ·Έλ ‡λ‹€λ©΄ μƒμ†μ˜ 단점을 μ œκ±°ν•˜λ©΄μ„œ, μœ„μ˜ 문제λ₯Ό ν•΄κ²°ν•  수 μžˆλŠ” 방법은 μ—†μ„κΉŒ? λ°”λ‘œ 상속이 μ•„λ‹Œ μœ„μž„μ„ ν™œμš©ν•˜λ©΄ λœλ‹€. 이λ₯Ό ν™œμš©ν•  수 μžˆλŠ” μ „λž΅ νŒ¨ν„΄μ„ λ‹€μŒ ν¬μŠ€νŒ…μ—μ„œ μ„œμˆ ν•΄λ³΄κ² λ‹€.

 

 

 

μ½”λ“œ 및 자료 좜처 :https://inf.run/5BUY

 

μŠ€ν”„λ§ 핡심 원리 - κ³ κΈ‰νŽΈ - μΈν”„λŸ° | κ°•μ˜

μŠ€ν”„λ§μ˜ 핡심 원리와 κ³ κΈ‰ κΈ°μˆ λ“€μ„ 깊이있게 ν•™μŠ΅ν•˜κ³ , μŠ€ν”„λ§μ„ μžμ‹ μžˆκ²Œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€., - κ°•μ˜ μ†Œκ°œ | μΈν”„λŸ°...

www.inflearn.com