본문 바로가기
JAVA/JAVA

템플릿 메소드(Template Method) 패턴 정리

by 개미가되고싶은사람 2024. 9. 10.

목차

    안녕하세요! 오늘은 소프트웨어 디자인에서 매우 중요한 개념인 템플릿 메소드(Template Method) 패턴에 대해 알아보겠습니다. 이 글을 통해 템플릿 메소드 패턴이 무엇인지, 어떻게 작동하는지, 그리고 실제 예제를 통해 이해해 보도록 하겠습니다. 😊

     

    Template Method Pattern이란?

    템플릿 메소드는 공통으로 사용하는 메서드를 템플릿화하여 부모 클래스에 뼈대를 두고, 구체적인 사항은 자식 클래스에서 구현하는 방식입니다. 이를 통해 자식 클래스는 알고리즘의 전체 구조를 변경하지 않고 특정 부분만 재정의할 수 있습니다. 즉, 부모 클래스에서는 전체적인 흐름을 템플릿으로 정의하고, 자식 클래스에서는 필요한 부분만 오버라이드하여 사용할 수 있습니다.

     

    템플릿 메서드 패턴 구조

    출저: https://refactoring.guru/ko/design-patterns/template-method

    • AbstractClass(부모 클래스) : 여러  메서드(일부는 추상 메소드일 수 있음)들을 선언하며, 이러한 메서드를 특정 순서로 호출하는 템플릿 메소드를 정의합니다.
    • ConcreteClass(자식 클래스) : AbstractClass를 상속받아 부모 클래스에서 정의된 추상 메소드를 구체적으로 구현합니다. 

     

    hook 메서드

    출저: https://refactoring.guru/ko/design-patterns/template-method

    훅 메소드는 템블릿 메소드 알고리즘의 특정 단계에서 호출되는 메소드로, 추가적인 동작이나 순서를 동적으로 제어할 수 있게 해줍니다.

    위에 사진을 보시면, 템플릿 메서드가 여러 단계를 포함하고 있고, step2 조건이 참인지 거짓인지에 따라 어떤 단계가 실행될지 결정되고, 추가적인 처리가 필요할 때 훅 메소드를 사용하는 걸 볼 수 있습니다. 훅 메소드는 템플릿 메소드의 전체 구조를 변경하지 않고도 동작을 조정할 수 있는 유연성을 제공합니다.

     

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

    장점

    • 템플릿 메소드는 다양한 하위 클래스에서 재사용할 수 있는 일반적인 동작을 캡슐화하여 코드 중복을 줄입니다.
    • 훅 메소드를 사용하면 전체 메서드를 재정의하지 않고도 알고리즘의 특정 부분을 수정하거나 확장할 수 있는 옵션이 자식 클래스에 제공됩니다.
    • 서브 클래스의 역할을 줄이고, 핵심 로직을 상위 클래스에서 관리하므로서 관리가 용이해진다


    단점

    • 자식 클래스에 사용자 정의가 필요한 단계가 많은 경우 템플릿 메서드 패턴으로 인해 계층 구조가 복잡해져 관리 및 이해가 더 어려워질 수 있습니다.
    • 알고리즘의 구조는 부모클래스에 정의되어 있으므로 알고리즘을 변경하려면 부모클래스를 수정해야 할 수 있으며 이로 인해 잠재적으로 모든 하위클래스에 영향을 줄 수 있습니다.
    • 서브클래스는 슈퍼클래스의 구조와 밀접하게 연결되어 있습니다. 부모클래스의 알고리즘을 변경해야 하는 경우 모든 종속 하위 클래스를 다시 검토해야 할 수 있습니다.

     

    간단한 코드로 템블릿 메소드 패턴 적용

    아래 예시에서는 축구선수와 농구선수의 훈련 루틴의 대한 코드입니다. 이 루틴은 준비운동, 기술 연습, 마무리 운동으로 구성됩니다. 축구선수와 농구선수는 기술 연습에서만 차이가 나지만, 그 외의 훈련 과정은 동일합니다. 템블릿 메소드 패턴을 적용하면 훈련 루틴의 일관성을 유지하면서도 각 스포츠의 특성을 반영할 수 있습니다.

     

    템플릿 메소드 패턴 적용 전

    public    class FootballPlayer {
            public void train() {
                warmUp();
                practiceFootball();
                coolDown();
            }
    
            private void warmUp() {
                System.out.println("준비운동");
            }
    
            private void practiceFootball() {
                System.out.println("축구 연습");
            }
    
            private void coolDown() {
                System.out.println("스트레칭으로 마무리");
            }
        }
    
    public    class BasketballPlayer {
            public void train() {
                warmUp();
                practiceBasketball();
                coolDown();
            }
    
            private void warmUp() {
                System.out.println("준비운동");
            }
    
            private void practiceBasketball() {
                System.out.println("농구 연습");
            }
    
            private void coolDown() {
                System.out.println("스트레칭으로 마무리");
            }
        }

     

     

    템플릿 메소드 적용 후

    public abstract class AbstractTemplate {
            // 템플릿 메서드: 훈련 루틴의 전체 구조를 정의
            public final void train() {
                warmUp();
                practiceSkills();
                coolDown();
            }
    
            // 공통 로직
            private void warmUp() {
                System.out.println("준비운동");
            }
    
            private void coolDown() {
                System.out.println("스트레칭으로 마무리");
            }
    
            // 추상 메서드: 구체적인 연습은 서브클래스에서 구현
            protected abstract void practiceSkills();
        }
    
        public class FootballPlayer extends AbstractTemplate {
            @Override
            protected void practiceSkills() {
                System.out.println("축구 연습");
            }
        }
    
        public class BasketballPlayer extends AbstractTemplate {
            @Override
            protected void practiceSkills() {
                System.out.println("농구 연습");
            }
        }

    앞에 말씀드렸지만 템블릿 메소드 패턴을 적용하면 일관된 훈련 루틴은 유지하면서도 각 스포츠의 특성을 반영한 훈련을 적용 할 수 있습니다.

     

    익명 내부 클래스 사용

    public abstract class AbstractTemplate {
        // 템플릿 메서드: 훈련 루틴의 전체 구조를 정의
        public final void train() {
            warmUp();
            practiceSkills();
            coolDown();
        }
    
        // 공통 로직
        private void warmUp() {
            System.out.println("준비운동");
        }
    
        private void coolDown() {
            System.out.println("스트레칭으로 마무리");
        }
    
        // 추상 메서드: 구체적인 연습은 서브클래스에서 구현
        protected abstract void practiceSkills();
    }
    
    public class Main {
        public static void main(String[] args) {
            // 축구 선수
            AbstractTemplate footballPlayer = new AbstractTemplate() {
                @Override
                protected void practiceSkills() {
                    System.out.println("축구 연습");
                }
            };
            footballPlayer.train();
    
            // 농구 선수
            AbstractTemplate basketballPlayer = new AbstractTemplate() {
                @Override
                protected void practiceSkills() {
                    System.out.println("농구 연습");
                }
            };
            basketballPlayer.train();
        }
    }

     

     

    hook 메소드를 사용한 예시

    abstract class AbstractTemplate {
        // 템플릿 메서드: 훈련 루틴의 전체 구조를 정의
        public final void train() {
            warmUp();
            practiceSkills();
            if (needAdditionalTraining()) {  // 훅 메서드
                additionalTraining();
            }
            coolDown();
        }
    
        // 공통 단계
        private void warmUp() {
            System.out.println("준비운동");
        }
    
        private void coolDown() {
            System.out.println("스트레칭으로 마무리");
        }
    
        // 추상 메서드: 서브클래스에서 기술 연습을 구체적으로 구현
        protected abstract void practiceSkills();
    
        // 훅 메서드: 서브클래스에서 추가훈련 여부를 결정
        protected boolean needAdditionalTraining() {
            return false;  // 기본적으로 추가 훈련은 필요하지 않다고 설정
        }
    
        // 추가 훈련 단계 (필요시 실행)
        protected void additionalTraining() {
            System.out.println("추가 훈련을 진행합니다.");
        }
    }
    
    class FootballPlayer extends AbstractTemplate {
        @Override
        protected void practiceSkills() {
            System.out.println("드리블과 슈팅을 연습합니다.");
        }
    
        @Override
        protected boolean needAdditionalTraining() {
            return true;  // 축구선수는 추가 훈련이 필요함
        }
    
        @Override
        protected void additionalTraining() {
            System.out.println("스프린트 훈련을 추가로 진행합니다.");
        }
    }
    
    class BasketballPlayer extends AbstractTemplate {
        @Override
        protected void practiceSkills() {
            System.out.println("드리블과 슛 연습을 합니다.");
        }
        
        // 농구선수는 추가 훈련이 필요하지 않음
    }

    축구선수와 농구선수의 훈련 과정은 동일한 구조를 따르지만, 축구선수는 추가적인 훈련을 진행합니다. 이를 구현하기 위해 훅 메서드 needAdditionalTraining()을 사용해, 축구선수는 추가 훈련을 하도록 설정하고 농구선수는 하지 않도록 구분했습니다.

     

    출력 결과



    Strategy패턴과 비교

    유사점

    두 패턴 모두 알고리즘의 특정 부분을 서브클래스에서 구현하도록 하고, 공통된 알고리즘의 구조를 상위 클래스에서 정의합니다.


    차이점

    전략 패턴은 여러 알고리즘을 유연하게 바꿀 수 있도록 도와주는 방법입니다. 이 패턴은 서로 다른 알고리즘을 객체로 만들어서 필요에 따라 쉽게 교체할 수 있게 해줍니다. 예를 들어, 자동차의 주행 모드를 바꾸는 것처럼, 상황에 맞는 알고리즘을 선택할 수 있습니다. 또한, 인터페이스를 통해 새로운 알고리즘을 추가하거나 기존 알고리즘을 수정할 수 있습니다.
    반면, 템플릿 메소드 패턴은 상속을 이용해 알고리즘의 기본 틀을 정의합니다. 이 패턴은 추상 클래스나 구체적인 클래스를 사용하여 클라이언트와 알고리즘을 연결합니다. 예를 들어, 요리 레시피를 생각해보면, 기본적인 조리 과정은 같지만 재료나 조리 방법을 바꿀 수 있는 것과 같습니다.

     

    결론은 전략 패턴과 템플릿 메소드 패턴의 결정적인 차이는 알고리즘을 유연하게 변경할 수 있는지 여부입니다.

     

     

     

     

    참고 자료

    [1] Inpa Dev 👨‍💻 - 템플릿 메소드(Template Method) 패턴 - 완벽 마스터하기 (https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%ED%85%9C%ED%94%8C%EB%A6%BF-%EB%A9%94%EC%86%8C%EB%93%9CTemplate-Method-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90)

    [2] Refactoring.Guru - 템플릿 메서드 패턴 (https://refactoring.guru/ko/design-patterns/template-method)

    [3] 티스토리 - 템플릿 메소드(Template Method) 패턴 설명 및 예제소스 (https://niceman.tistory.com/142)

    [4] 느리더라도 꾸준하게 - [디자인 패턴] 템플릿 메소드(Template Method) 패턴이란? (https://steady-coding.tistory.com/384)