Template Callback Pattern(템플릿 콜백 패턴) - 견본 /회신 패턴

2024. 1. 11. 12:54Design Pattern

[목차]

1. 게시물을 작성한 이유
2. 설명
3. 예제
4. Refactoring 
5. 결론
6. 추가 : System.out.println(제외하기) 

1.  게시물을 작성한 이유

회사에서 운영을 하면서 여러가지 요구조건이 있을때 참으로 아쉬운 점이 많았다. 그 아쉬운점은 여러가지 요구사항을 듣고 그것을 패턴화를 못했기 때문이다. 물론 지식적인 부분에 부족한 점이 있을 수 있지만,  공부를 한 부분도 딱 떠오르지가 않았다. 마침 '스프링 객체지향과 원리' 라는 책을 읽었는데 거기서 스프링이 자주 사용하는 템플릿콜백 패턴이라고 해서 블로그 글을 작성하려고 한다. 


2. 설명 

템플릿 콜백 패턴에 대해서 설명하겠다. 스프링의 3대 프로그래밍 모델 중 하나인 DI에서 사용하는 특별한 형태의 전략패턴이다. 템플릿 콜백 패턴은 전략패턴의 변형형태이다. 
콜백 패턴은 한문장으로 정리하면 

"전략을 익명 내부 클래스로 구현한 전략패턴"

이렇게 설명 할수가 있다. 즉 전략 패턴을 조금 변형한 형태라는 것을 알 수 있다. 이러한 것을 한번 코드로 살펴보자 

3. 예제 

Strategy.java

public interface Strategy {
	public abstract void runStrategy();
}

인터페이스 Strategy를 만든다. 이 runStrategy method를 통해서 다양한 전략을 구현 할수 있다.

Robot.java

package pattern.templatecallback;

public class Robot {
    void runContext(Strategy strategy){
        System.out.println("이동 시작");
        strategy.runStrategy();
        System.out.println("이동 종료");
    }
}

strategy를 파라미터로 받는다. 여기서 다양한 전략을 유연성있게 받을 수 있다. 

Human.java

package pattern.templatecallback;

public class Human {
    public static void main(String[] args) {
        Robot ironMan = new Robot();

        ironMan.runContext(new Strategy() {
            @Override
            public void runStrategy() {
                System.out.println("슝 날아가다!");
            }
        });

        System.out.println();

        ironMan.runContext(new Strategy() {
            @Override
            public void runStrategy() {
                System.out.println("총총총 뛰어가다");
            }
        });

        System.out.println();

        ironMan.runContext(new Strategy() {
            @Override
            public void runStrategy() {
                System.out.println("기어가다...");
            }
        });
    }
}

Human 객체는 로봇객체를 만들어서 실제 움직이는 메서드를 실행한다. 여기서 어떻게 움직이느냐는 전략에 따라 달라진다. 익명 클래스를 파라미터로 받아가지고 예제 파일에서는 실행하였다. 

[결과]

java 실행 후

여기를 보면 모든 움직임에는 "이동시작"과 "이동종료"가 포함되어있다. runContext method 부분에 앞 뒤에 이동시작과 이동 종료를 추가하고 그 가운데에 핵심 전략을 실행한 모습이다. 

4. ReFactoring 

Human을 보면 중복된 코드가 있다. 바로 익명클래스가 파라미터로 받는 부분인데 이 것을 줄이는 방법도 존재한다.

4-1 lamda 이용

  lamda를 이용해서 코드를 간소화 할수 있다. 아래처럼 코드를 간소화할 수 있다.

ironMan.runContext(() -> System.out.println("기어가다..."));

4-2 Robot 객체 내부에서 전략을 생성

Robot객체 안에서 전략을 생성하는 방법이 있다. 말로 설명하는 것보다 코드로 표현하는게 더 이해하기가 편하다. 

Robot.java

package pattern.templatecallback;

public class Robot {
    void runContext(String runSound){
        System.out.println("이동 시작");
        executeRun(runSound).runStrategy();
        System.out.println("이동 종료");
    }

    private Strategy executeRun(final String runSound) {
        return () -> System.out.println(runSound);
    }
}


Human.java

package pattern.templatecallback;

public class Human {
    public static void main(String[] args) {
        Robot ironMan = new Robot();

        ironMan.runContext("슝 날아가다!");

        System.out.println();

        ironMan.runContext("총총총 뛰어가다");

        System.out.println();

        ironMan.runContext("기어가다...");
    }
}

Robot 객체 내부에서 전략을 생성하는 방식으로 바꾸었더니 Human 객체를 간편하게 리팩토링 할 수 있다. 

5. 결론

항상 중복된 코드를 어떻게 줄일까는 숙제인거 같다. 이 책을 보지 않았다면 맨 처음 단계에서 끝났을거 같은데 리팩토링 하는 방법까지 나와있어서 좋았다. 그리고 템플릿 콜백 메서드는 약간 AOP와 비슷하다고 생각이 든다. 앞에 로직이 실행되고 가운데 핵심로직(전략)을 실행 후 마지막부분에 로직을 공통적으로 실행해주기 때문이다. 좀 더 설계와 개발을 잘하려면 다양한 패턴을 더 공부해야겠다.   

6. 추가 : System.out.println(제외하기) 

블로그 글 피드백으로 System.out.println에 대해서 알아보고 제외시켜서 실행해보려고 한다. System.out.println을 왜 제외해야하는가? 라는 부분은 따로 글로 자세하게 설명하겠다. 하지만 간략히 설명하자면 
1) 로그레벨을 조절할 수 없어서 표준출력으로 반드시 출력이 된다. 
2) System.out.println는 synchonized로 되어있어서 임계영역이 되어버리기 때문에 멀티쓰레드 환경에서 사용하면 성능저하가 발생한다.
3)  파일로 저장하지 않고 휘발성으로 로그정보가 날라간다. 로그라는 것은 에러가 나거나 어떤 특정상황에서 값이나 로직확인을 위해 필요한데 파일로 저장해놓으면 히스토리로 보기가 편하다. 하지만 System.out.println은 한번만 출력이 되기 때문에 파일로 저장하는 것보다 불편하다.

따라서 System.out.println -> log로 수정한 버전을 첨부한다. 

Robot.java

package org.example.pattern.strategy;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Robot {
    public static final Logger logger = LogManager.getLogger(Robot.class);

    void runContext(String runSound) {
        logger.info("달리기 시작");
        executeRun(runSound).runStrategy();
        logger.info("달리기 끝\n");

    }

    private Strategy executeRun(final String runSound) {
        return () -> logger.info(runSound);
    }
}

Human.java

package org.example.pattern.strategy;

public class Human {
    public static void main(String[] args) {
        Robot ironMan = new Robot();
        ironMan.runContext("슝 날아가다!");
        ironMan.runContext("총총총 뛰어가다");
        ironMan.runContext("기어가다...");
    }
}

[느낀점]

무심코 System.out.println으로 로직확인을 했던 내 습관을 고쳐야겠다는 생각을 하였다. 자칫 운영으로 반영 될수도 있고, 단순히 로직확인하지만 운영에서 성능이슈가 발생할수도 있기 때문이다. 아직도 더 배울점들이 많은 것 같다

[참고자료]
https://velog.io/@destiny1616/System.out.println-%EC%82%AC%EC%9A%A9%EC%9D%84-%EC%9E%90%EC%A0%9C%ED%95%B4%EC%95%BC-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0
https://systemdata.tistory.com/21

https://product.kyobobook.co.kr/detail/S000001628116

 

System.out.println() 사용을 자제해야 하는 이유

왜 프로덕션 코드에서 System.out.println() 말고 Logger나 Log4j 같은 로깅 프레임워크를 사용해야 할까? 크게 2가지 이유가 있다.여러 로깅프레임워크는 로그 레벨에 따라 디버깅 정보를 로그하게끔 해

velog.io

 

System.out.println 메소드는 실무에서 `절대 사용하지마라.`

목차개요System.out.println 무엇인가?왜 사용해서는 안되는가?로그를 남기면 안되는 것인가?결론1. 개요프로그래밍을 처음 접하면 System.out.println(”Hello World”);같이 콘솔에 출력하는 것을 배울 것이

systemdata.tistory.com

 

스프링 입문을 위한 자바 객체 지향의 원리와 이해 | 김종민 - 교보문고

스프링 입문을 위한 자바 객체 지향의 원리와 이해 | 이 책은 자바에서 스프링으로 나아가기 위한 연결 고리를 제공한다.

product.kyobobook.co.kr

 

'Design Pattern' 카테고리의 다른 글

Strategy(전략) 패턴  (1) 2024.02.10
Builder Pattern  (1) 2023.10.09