👨‍💻 템플릿 메소드 패턴이란?


어떠한 알고리즘의 골격(템플릿 메소드)을 갖고있는 추상 클래스 혹은 인터페이스를 정의하고
일부 혹은 특정 단계를 서브클래스에서 구현하도록 하여
알고리즘의 전체적인 구조를 유지한 체 재사용성에 초점을 둔 디자인 패턴


예시) 벤치프레스

public class BenchPress {
	public void deliciousMuscle() {
    	while (!knockDown) {
        	addWeight();
        	liftBar();
        	liftDownBar();
        }
    }
    
	public void liftBar() {
    	System.out.println("바벨을 가슴을 이용해서 든다.");
    }
    
    public void addWeight() {
    	System.out.println("플레이트를 바벨에 추가한다.");
    }
    
    public void liftDownBar() {
    	System.out.println("바벨을 내린다.");
    }
}

예시) 데드리프트

public class DeadLift {
	public void deliciousMuscle() {
    	while (!knockDown) {
        	addWeight();
        	liftBar();
        	liftDownBar();
        }
    }
    
	public void liftBar() {
    	System.out.println("바벨을 하체와 등을 이용해서 든다.");
    }
    
    public void addWeight() {
    	System.out.println("플레이트를 바벨에 추가한다.");
    }
    
    public void liftDownBar() {
    	System.out.println("바벨을 내린다.");
    }
}

위 두가지의 운동에 대한 메소드가 있다.
각각의 운동은 비슷하면서도 다르면서도 같은부분이 있으면서도 다른부분이 있다.
하지만 바벨을 들고 운동한다는 공통점이 있다.

이때 바벨을 들고 운동하는 것(특정 알고리즘)에 대한 골격을 정의하는 것이
바로 템플릿 메소드 패턴의 핵심이다.


예시) 바벨운동

public abstract class BabelWork {
	final void deliciousMuscle() { // 알고리즘 골격(템플릿 메소드)
    	while (!knockDown) {
        	addWeight();
        	liftBar();
        	liftDownBar();
        }
    }
    
    public void liftBar(); // 특정 단계를 서브클래스에서 구현하도록
    
    public void addWeight() {
    	System.out.println("플레이트를 바벨에 추가한다.");
    }
    
    public void liftDownBar() {
    	System.out.println("바벨을 내린다.");
    }
}

이때, 운동하는행동에 대한 골격을 final로 정의한 것은
알고리즘 단계를 서브클래스가 마음대로 건드리지 못하게 하기 위해서이다.

이때, 저수준 단계에서 고수준 단계의 알고리즘 골격의 특정 단계에 끼어들기 위해서
후크(hook)라는 메소드를 사용한다.



🔎 후크(hook)란?


후크는 추상클래스에서 선언되지만 기본적인 내용만 구현되어있거나 아무 코드도 들어있지 않는 메소드를 말한다.
이때, 서브클래스에서는 이를 오버라이드하면서 다양한 용도로 사용이 가능하다. (헤드퍼스트 디자인 패턴 인용)

위의 예시를 다시 가져온 뒤에 hook 예시를 추가해보면,

public abstract class BabelWork {
	final void deliciousMuscle() { // 알고리즘 골격(템플릿 메소드)
    	while (!knockDown) {
        	addWeight();
        	liftBar();
            if (wantHelp()) {
            	help();
            }
        	liftDownBar();
            if (wantHelp()) {
            	help();
            }
        }
    }
    
    public void liftBar(); // 특정 단계를 서브클래스에서 구현하도록
    public void help(); // 후크(hook)를 이용해서 알고리즘 골격 사이에 끼어들기
    
    public void addWeight() {
    	System.out.println("플레이트를 바벨에 추가한다.");
    }
    
    public void liftDownBar() {
    	System.out.println("바벨을 내린다.");
    }
    
    public boolean wantHelp() {
    	return false;
    }
}

wantHelp()라는 값이 추상클래스에서 구현된 값은 항상 false이기 때문에
이를 그대로 상속받아 추상메소드만 오버라이드한 경우의 서브클래스는
무게를 추가하고 -> 바를 들고 -> 바를 내린다가 일반적인 구조일 것이다.

하지만, 서브클래스에서 wantHelp() 함수를 재정의하여 리턴값을 true로 설정하게 되면,
도움이 필요한 헬린이, 무게를 무리해서 친 사람 클래스들의 운동 구조는
무게를 추가하고 -> 바를 들고 -> 트레이너의 도움을 받고 -> 바를 내리고 -> 트레이너의 도움을 받는다가 될 것이다.



🔎 템플릿 메소드 패턴의 장단점


장점

  1. 중복을 줄이고 코드의 재사용성을 확보할 수 있다.
  2. 핵심 알고리즘 로직의 관리가 용이하다.
  3. 추상메소드 재정의를 통한 서브클래스의 확장에 유리하다.

단점

  1. 템플릿 메소드의 각 단계가 무수한 추상메소드로 관리된다면 모두 재정의해주어야 하므로 복잡도가 증가할 수 있다.



♻️ 추가) 할리우드 원칙과 의존성 부패?


의존성 부패(Dependency Rot)

의존성이 얽히고 설켜 복잡하게 꼬여있는 상황을 말한다.
저수준 구성요소와 고수준 구성요소가 위아래 없이 계급장 다 떼고 패싸움하는 거라고 보면 된다.

할리우드 원칙(Hollywood Principle)

"Don't call us, we'll call you"("우리한테 연락하지 마세요. 우리가 먼저 연락합니다.")
라는 말에서 유래한 원칙으로


저수준의 구성요소가 고수준의 구성요소에 접근할 수는 있어도 직접 호출할 수 없고
고수준의 구성요소에 의해 어떻게 쓰일지 결정하게 해야 한다는 원칙이다.


이러한 방식은 IoC(Inversion of Control, 제어의 역전), DI(Dependecy Injection: 의존성 주입)으로도 불린다.
Spring Framework에서도 자주 등장하는 IoC와 DI를 여기서 확인할 수 있다.
하나만 기억하면 된다.
저수준의 구성요소가 고수준의 구성요소를 직접적으로 호출할 수 없고 고수준의 구성요소에 의해 정해진다.


이렇게 저수준의 구성요소가 선을 지키면서 고수준의 구성요소를 직접적으로 호출하지 않으면
자연스럽게 의존성이 꼬이지 않고 위에서 아래로 내려가는
즉, SOLID 디자인 원칙의 DIP 의존성 역전 법칙이 잘 지켜지면서 의존성 부패 현상도 막을 수 있게 된다.

profile
백엔드를 사랑하는 초보 개발자

0개의 댓글