maeng0830
뇌 채우기 운동
maeng0830
전체 방문자
오늘
어제
  • maeng0830-note (85)
    • 자바 (3)
    • 스프링 (39)
      • Core (21)
      • DB (16)
      • Security (2)
      • Test (0)
    • 자료구조 & 알고리즘 (19)
      • 자료구조 (12)
      • 알고리즘 (7)
    • 다른 개발 도구들 (4)
      • Git&Github (1)
      • Redis (3)
    • 프로젝트 (9)
      • Album (9)
    • CS (10)
      • 운영체제 (5)
      • 데이터베이스 (5)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 자료구조
  • JPA
  • 트랜잭션
  • spring security
  • JPQL

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
maeng0830

뇌 채우기 운동

스프링/Core

디자인 패턴-템플릿 메서드 패턴

2023. 10. 5. 00:14

좋은 코드의 기준 중 하나는 변하는 부분과 변하지 않는 부분을 분리하는 것이다.

 

일반적으로 변하는 부분이라 함은 비즈니스 로직과 같은 핵심 기능을 의미한다.

그리고 변하지 않는 부분은 로그 기능, 트랜잭션 기능 등 보조적으로 사용되는 부가 기능을 의미한다.

 

템플릿 메서트 패턴은 변하는 부분과 변하지 않는 부분을 분리하기 위해 사용되는 디자인 패턴이다.

 

템플릿 메서드 패턴

템플릿 메서드 패턴은 부모 클래스에 변하지 않는 템플릿을 구현하고, 변하는 부분은 자식 클래스에서 오버라이딩 해서 구현한다. 이것을 통해 변하지 않는 부분과 변하는 부분을 분리할 수 있다.

 

그리고 템플릿 내부에서 변하는 부분을 호출하여 변하지 않는 부분과 변하는 부분을 통합한다.

 

템플릿 매서드 패턴 미사용

우선 템플릿 메서드 패턴을 사용하지 않는 코드를 통해 템플릿 메서드의 필요성을 알아보자.

 

templateMethodV0

templateMethodV0 내부에서는 logic1()과 logic2()가 호출된다.

 

logic1()과 logic2()는 변하는 부분인 비즈니스 로직과 변하지 않는 부분인 시간측정 로직이 혼재되어있다.

앞에서 말했듯이 변하는 부분과 변하지 않는 부분, 즉 핵심 기능과 부가 기능이 혼재되어있는 코드는 좋은 코드가 아니다.

또한 변하지 않는 부분을 위해 두 메소드에 중복된 코드가 작성되어있다.

그리고 만약 변하지 않는 부분에 변경이 있을 경우, 변하지 않는 부분이 적용된 메소드를 모두 수정해야한다.

@Slf4j
public class TemplateMethodTest {

	@Test
	void templateMethodV0() {
		logic1();
		// 비즈니스 로직1 실행
		// resultTime=4
		logic2();
		// 비즈니스 로직2 실행
		// resultTime=0
	}

	// 핵심기능인 비즈니스 로직과 부가기능인 시간측정 로직이 혼재되어있다.
	// 시간측정 로직은 변하지 않으며, logic2에도 중복 작성되어있다.
	// 또한 부가기능의 변경이 있을 경우, 부가기능이 적용된 메소드를 모두 수정해야한다.
	private void logic1() {
		long startTime = System.currentTimeMillis();
		// 비즈니스 로직 실행
		log.info("비즈니스 로직1 실행");
		// 비즈니스 로직 종료
		long endTime = System.currentTimeMillis();
		long resultTime = endTime - startTime;
		log.info("resultTime={}", resultTime);
	}

	// 핵심기능인 비즈니스 로직과 부가기능인 시간측정 로직이 혼재되어있다.
	// 시간측정 로직은 변하지 않으며, logic1에도 중복 작성되어있다.
	// 또한 부가기능의 변경이 있을 경우, 부가기능이 적용된 메소드를 모두 수정해야한다.
	private void logic2() {
		long startTime = System.currentTimeMillis();
		// 비즈니스 로직 실행
		log.info("비즈니스 로직2 실행");
		// 비즈니스 로직 종료
		long endTime = System.currentTimeMillis();
		long resultTime = endTime - startTime;
		log.info("resultTime={}", resultTime);
	}
}

 

템플릿 메서드 패턴 사용

이제 템플릿 메서드 패턴을 적용하여 핵심 기능과 부가 기능, 즉 변하는 부분과 변하지 않는 부분이 어떻게 분리될 수 있는 지 살펴보자.

 

AbstractTemplate

AbstractTemplate에는 execute()와 call()이 존재한다.

 

execute()는 변하지 않는 부분, 즉 부가 기능이 구현되어 있는 메소드이다. 즉 템플릿이라고 할 수 있다.

execute() 내부에서는 AbstractTemplate의 자식 클래스에서 재정의될 call()을 호출한다.

 

call()은 변하는 부분, 즉 핵심 기능이 실행되는 메소드이다.

AbstractTemplate의 자식 클래스에서 재정의 된다.

@Slf4j
public abstract class AbstractTemplate {

	// 변하지 않는 부분, 즉 부가 기능이 구현되어 있는 메소드이다. 즉 템플릿이라고 할 수 있다.
	// 자식 클래스에서 핵심 기능이 구현될 call()을 내부에서 호출한다.
	// 부가기능의 변경이 필요하더라도, 해당 부분에서만 수정하면 된다.
	public void execute() {
		long startTime = System.currentTimeMillis();
		// 비즈니스 로직 실행
		call(); // 변하는 부분, 즉 핵심 기능
		// 비즈니스 로직 종료
		long endTime = System.currentTimeMillis();
		long resultTime = endTime - startTime;
		log.info("resultTime={}", resultTime);
	}

	// 변하는 부분, 즉 핵심 기능이 실행되는 메소드이다.
	// 자식 클래스에서 오버라이딩 한다.
	protected abstract void call();
}

 

SubClassLogic

SubClassLogic은 AbstractTemplate의 자식 클래스이다.

SubClassLogic에서 목적에 따라 call()이 재정의된다.

 

SubClassLogic1의 call()은 비즈니스 로직1이 실행되도록 구현되고,

SubClassLogic2의 call()은 비즈니스 로직2가 실행되도록 구현된다.

@Slf4j
public class SubClassLogic1 extends AbstractTemplate {

	// 자식 클래스의 핵심 기능에 따라 구현될 부분이다.
	// 부가기능과 관련된 코드 없이, 핵심 기능에 대한 코드만 작성할 수 있다.
	@Override
	protected void call() {
		log.info("비즈니스 로직1 실행");
	}
}


@Slf4j
public class SubClassLogic2 extends AbstractTemplate {

	// 자식 클래스의 핵심 기능에 따라 구현될 부분이다.
	// 부가기능과 관련된 코드 없이, 핵심 기능에 대한 코드만 작성할 수 있다.
	@Override
	protected void call() {
		log.info("비즈니스 로직2 실행");
	}
}

 

templateMethodV1()

templateMethodV1() 내부를 살펴보자.

SubClassLogic1과 SubClassLogic2의 참조 변수인 template1과 template2가 선언된다.

 

template1.execute()를 호출하면, 변하지 않는 execute() 내부에서 비즈니스 로직1이 호출되어 실행된다.

template2.execute()를 호출하면, 본하지 않는 execute() 내부에서 비즈니스 로직2가 호출되어 실행된다.

 

결과적으로 템플릿 메서드 패턴을 사용하여 다음과 같은 결과를 얻을 수 있었다.

  • 변하는 부분과 변하지 않는 부분, 즉 핵심 기능과 부가 기능을 분리할 수 있었다.
  • 부가 기능에 변경이 필요하더라도, 템플릿만 수정하면 되기 때문에 유지 보수의 효율성이 향상되었다.
@Slf4j
public class TemplateMethodTest {

	// 템플릿 메서드 패턴 적용
	@Test
	void templateMethodV1() {
		AbstractTemplate template1 = new SubClassLogic1();
		template1.execute();
		// 비즈니스 로직1 실행
		// resultTime=0
		AbstractTemplate template2 = new SubClassLogic2();
		template2.execute();
		// 비즈니스 로직1 실행
		// resultTime=1
	}
}

 

템플릿 메서드 패턴 사용 - 익명 내부 클래스 사용

위의 예시에서 알 수 있듯이 템플릿 메서드 패턴은 변하는 부분을 재정의할 자식 클래스를 지속적으로 생성해야한다는 단점이 있다.

 

클래스의 객체를 생성하면서 동시에 해당 클래스를 상속하는 자식 클래스를 정의할 수 있는 익명 내부 클래스를 통해 이러한 단점을 해결할 수 있다.

 

아래의 코드를 보면서 익명 내부 클래스를 통해 템플릿 메소드 패턴이 어떻게 사용되는 지 참고하자.

 

templateMethodV2()

@Slf4j
public class TemplateMethodTest {

	// 템플릿 메서드 패턴 적용 + 익명 내부 클래스
	// 익명 내부 클래스를 통해 템플릿 메서트 패턴 적용을 위한 자식 클래스 생성을 방지할 수 있다.
	@Test
	void templateMethodV2() {
		AbstractTemplate template1 = new AbstractTemplate() {
			@Override
			protected void call() {
				log.info("비즈니스 로직1 실행");
			}
		};

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

		template1.execute();
		// 비즈니스 로직1 실행
		// resultTime=4
		template2.execute();
		// 비즈니스 로직2 실행
		// resultTime=0
	}
}

 

템플릿 메서드 패턴의 단점

템플릿 메서드 패턴은 상속을 사용한다. 따라서 상속으로 인한 단점이 그대로 발생한다.

 

우선 변하는 부분이 재정의 되는 자식 클래스는 부모 클래스의 기능을 전혀 사용하지 않는다.

그럼에도 불구하고 템플릿 메서드 패턴을 위해 자식 클래스는 부모 클래스를 상속해야하는 것이다. 

 

그리고 상속을 위해 별도의 자식 클래스나, 익명 내부 클래스를 사용하는 것도 꽤나 불편하다.

 

템플릿 메서드 패턴과 비슷한 기능을 제공하면서, 템플릿 메서드 패턴의 단점을 해결할 수 있는 디자인 패턴이 전략 패턴이다. 

    '스프링/Core' 카테고리의 다른 글
    • 디자인 패턴-프록시, 데코레이터 패턴
    • 디자인 패턴-전략 패턴
    • Thread Local
    • HTTP 요청 데이터, HTTP 응답 데이터
    maeng0830
    maeng0830

    티스토리툴바