1. 템플릿 메서드 패턴 - midoBanDev/design-pattern-java GitHub Wiki

템플릿 메서드 패턴

  • 작업에서 알고리즘의 골격을 정의하고 일부 단계를 하위 클래스로 연기한다.
    템플릿 메서드를 사용하면 하위 클래스가 알고리즘의 구조를 변경하지 않고도 알고리즘의 특정 단계를 재정의 할 수 있다.

  • 부모 클래스에 알고리즘의 골격인 템플릿을 정의하고, 일부 변경되는 로직은 자식 클래스에 정의하는 것이다.
    이렇게 하면 자식 클래스가 알고리즘의 전체 구조를 변경하지 않고, 특정 부분만 재정의할 수 있다.
    결국 상속과 오버라이딩을 통한 다형성으로 문제를 해결하는 것이다.

image

상속의 단점

  • 자식 클래스가 부모 클래스와 컴파일 시점에 강하게 결함되는 문제가 있다. 이것은 의존관계의 문제이다. 자식 클래스는 실제로 부모 클래스의 기능을 전혀 사용하고 있지 않지만 템플릿 메서드 패턴을 위해 자식 클래스는 부모 클래스를 상속받고 있다. 즉, 자식 클래스는 부모의 기능을 전혀 사용하고 있지 않는데 부모 클래스를 알아야 한다. 이것은 좋은 설계가 아니다.

    자식이 부모에 의존하고 있기 때문에 부모가 변경되는 상황에 그대로 영향을 받을 수 밖에 없다. 추가로 템플릿 메서드 패턴은 상속 구조를 사용하기 때문에 별도의 클래스나 익명 내부 클래스를 만들어야 하는 부분도 복잡하다.

좋은 설계란?

  • 변경이 일어날 때 자연스럽게 드러난다.
  • 로그는 남기는 부분을 모듈화 하고, 비즈니스 부분을 분리했다. 만약 로그 남기는 부분을 변경해야 한다고 했을 때 AbstractTemplate 부분만 수정해주면 된다.

Code

부모 클래스 (템플릿)

@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();
}

자식 클래스

@Slf4j
public class SubClassLogic1 extends AbstractTemplate{

	@Override
	protected void call() {
		log.info("비즈니스 로직2 실행");
	}

}
@Slf4j
public class SubClassLogic2 extends AbstractTemplate{

	@Override
	protected void call() {
		log.info("비즈니스 로직2 실행");
	}

}

템플릿 패턴 다양한 사용

@Slf4j
public class TemplateMethodTest {

	@Test
	void templateMethodVO() {
		logic1();
		logic2();
	}
	
	private void logic1() {
		long startTime = System.currentTimeMillis();
		
                // 비즈니스 로직 실행
		log.info("비즈니스 로직1 실행");
		// 비즈니스 로직 종료
		
                long endTime = System.currentTimeMillis();
		long resultTime = endTime - startTime;
		
		log.info("resultTime={}", resultTime);
	}
	
	private void logic2() {
		long startTime = System.currentTimeMillis();

		// 비즈니스 로직 실행
		log.info("비즈니스 로직2 실행");
		// 비즈니스 로직 종료

		long endTime = System.currentTimeMillis();
		long resultTime = endTime - startTime;
		
		log.info("resultTime={}", resultTime);
	}
	
	/**
	 * 템플릿 메서드 패턴 사용
	 * - 변하지 않는 로직과 변화를 로직을 분리한다.
	 * 1. 변하지 않는 로직을 정의할 AbstractTemplate class(abstract)를 만든다. 
	 *    그리고 변하는 로직을 재정의 할 call() method(abstact)를 정의한다.
	 *    변하지 않는 로직은 execute() 메서드에 정의한 후 call() 메서드는 호출한다.   
	 * 2. 변하는 로직을 정의할 SubClassLogic1,2 class를 만든다. 그리고 AbstractTemplate class(abstract)를 상속 받은 후 
	 *    call() method(abstact)를 오버라이딩 해서 변하는 로직을 정의한다. 
	 */
	@Test
	void templateMethodV1() {
		AbstractTemplate template1 = new SubClassLogic1();
		template1.execute();
		
		AbstractTemplate template2 = new SubClassLogic2();
		template2.execute();
	}
	
	/**
	 * 익명 내부 클래스 사용
	 * - 추상 클래스를 상속 받을 클래스를 만들지 않고, 
	 *   추상 클래스 인스턴스를 생성 한 후 추상 클래스 안에 있는 추상 메서드를 바로 오버라드 해서 사용하는 방법이다.
	 *   이렇게 하면 굳이 자식 클래스를 만들지 않아도 된다. 
	 */
	@Test
	void templateMethodV2() {
		AbstractTemplate template1 = new AbstractTemplate() {
			@Override
			protected void call() {
				log.info("비즈니스 로직1 실행");
			}
		};
		log.info("클래스1 name={}", template1.getClass());
		template1.execute();
		
		AbstractTemplate template2 = new AbstractTemplate() {
			@Override
			protected void call() {
				log.info("비즈니스 로직2 실행");
			}
		};
		log.info("클래스2 name={}", template2.getClass());
		template2.execute();
	}
}