Spring Boot/디자인패턴

[Spring] Spring에서 사용되는 템플릿 메소드 패턴

수수한개발자 2023. 4. 29.
728x90

템플릿 메소드 패턴 

GOF의 디자인 패턴에서는 템플릿 메서드 패턴을 다음과 같이 정의하고 있다.

 

템플릿 메서드 디자인 패턴의 목적은 다음과 같습니다.
작업에서 알고리즘의 골격을 정하고 일부 단계를 하위 클래스로 연기합니다. 
템플릿 메서드를 사용하면 클래스가 알고리즘의 구조를 변경하지 않고도 알고리즘의 특정 단계를 재 정의할 수 있습니다.

 

쉽게 말해 부모 클래스에 기본적인 알고리즘의 흐름을 구현하고 중간에 필요한 처리를 자식 클래스에게 위임하는 패턴을 템플릿 메서드 패턴이라고 합니다.

 

 

템플릿 메소드 패턴 예제

붕어빵을 만드는 과정이 다음과 같다고 가정해보겠습니다.

슈크림 붕어빵 팥 붕어빵
1. 반죽을 넣는다.
2. 슈크림을 넣는다.
3. 반죽을 넣는다.
4. 3분 기다린다.
1. 반죽을 넣는다.
2. 팥을 넣는다.
3. 반죽을 넣는다.
4. 4분 기다린다.
public class CreamPuffBread {

    public void firstDough() {
        System.out.println("반죽을 넣는다.");
    }

    public void addCreamPuffs() {
        System.out.println("슈크림을 넣는다.");
    }

    public void secondDough() {
        System.out.println("반죽을 넣는다.");
    }

    public void waitForMinutes() {
        System.out.println("3분 기다린다.");
    }
}


public class RedBeanBread {
    public void firstDough() {
        System.out.println("반죽을 넣는다.");
    }

    public void addCreamPuffs() {
        System.out.println("팥을 넣는다.");
    }

    public void secondDough() {
        System.out.println("반죽을 넣는다.");
    }

    public void waitForMinutes() {
        System.out.println("4분 기다린다.");
    }
}

 

위의 코드들을 보면 반죽을 넣는 행위는 완전히 동일한 코드이다. 따라서 Bread라는 클래스를 정의하여 중복된 코드를 제거 할 수 있다. 단순히 동일한 메소드만 뺴내는것에서 끝나는것이 아니라 여전히 무언가를 넣는 행위와 몇분 기다린다닌 행위가 유사하다. 템플릿 메서드 패턴은 이것도 추상화하는것이 목표라고 할 수 있다.

 

public abstract class Bread {
    public void makeBread() {
        putDough();
        putExtra();
        putDough();
        waitMinutes();
    }

    public void putDough() {
        System.out.println("반죽을 넣는다.");
    }

    public abstract void putExtra();

    public abstract void waitMinutes();
}

public class CreamPuffBread extends Bread {
    @Override
    public void putExtra() {
        System.out.println("슈크림을 넣는다.");
    }

    @Override
    public void waitMinutes() {
        System.out.println("3분 기다린다.");
    }
}

public class RedBeanBread extends Bread {

    @Override
    public void putExtra() {
        System.out.println("팥을 넣는다.");
    }

    @Override
    public void waitMinutes() {
        System.out.println("3분 기다린다.");
    }
}

 

위와 같이 하위 클래스에서 각자의 행위를 재정의하면 된다.

따라서 결과적으로 위의 사진과 같은 구조를 Bread 클래스가 가지게 되고, bread.makeBread() 를 호출하면 붕어빵이든, 팥 붕어빵이든 붕어빵을 만드는 행위를 다형성 있게 구현할 수 있습니다.

이렇게 템플릿 메소드 패턴은 같은 역할을 하는 메소드 이지만 다른 형태로 사용이 필요한 경우 효과적으로 적용할 수 있다.

 

자바와 스프링에서 템플릿 메소드 패턴을 적용한 사례

public class Template extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPost(req, resp);
    }
}

 

위의 코드는 내가 위의 코드가  실행시키는것이 아닌 서블릿 컨테이너 엔진이 요청이 들어오면 오버라이드한 메소드들을 실행 시켜주게 됩니다. 

이러한 부분이 IOC가 적용된 모습입니디. 이 코드를 제어하는 부분이 위 코드 스스로에게 있는것이 아니라 어떤 외부에 있다.

-> 서블릿 컨테이너에게 있다.

템플릿 메소드 패턴을 적용하는 부분이 대부분 이런식으로 작동 되게 됩니다. 

 

이제 스프링을 살펴보겠습니다.

 

Spring에서 template method pattern은 가장 핵심인 DispatcherServlet에서 사용되고 있습니다. Spring의 DispatcherServlet은 Http요청에 대해서 처리합니다. 이러한 DispatcherServlet은 Servlet을 상속 받은 것으로 구현되어 있습니다. 그리고 해당 Class는 상속 구조를 가지고 있습니다. 즉, 아래와 같습니다.

 

 

 DispatcherServlet doService() 라는 method가 있습니다. 이 method는 실질적으로 http 요청에 대해서 처리하는 method입니다. 해당 method가 template method pattern으로 구현되어있습니다.

 

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    ...중략...
    protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        ...중략...

        initContextHolders(request, localeContext, requestAttributes);

        try {
            doService(request, response); // template method pattern 이용
        }
        ...중략...
    }
    ...중략...

    protected abstract void doService(HttpServletRequest request, HttpServletResponse response) throws Exception; // subClass에게 위임

    ...중략...
}

 

public class DispatcherServlet extends FrameworkServlet {

    ...

    @Override
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        logRequest(request);

        ...중략...

    }

    ...
}

 

FrameworkServlet에서 doService에 대한 구현은 하위클래스에게 위임하고 있는것을 확인할 수 있었습니다.  그리고 DispatcherServlet은 doService를 구체화하고 있습니다. 그래서 우리가 DispatcherServlet을 구현하여 processRequest를 호출하면 frameworkServlet의 processRequest의 로직을 타다 doService(request, response)를 만나면 DispatcherSerlvet의 로직을 타게 되는것입니다.

 

 

템플릿 메소드 패턴의 단점

템플릿 메소드 패턴의 단점은 상속을 사용한다는 것입니다.

따라서 상속에서 오는  문제점을 그대로 안고간다.  상속을 받는 다는 것은 자식 클래스가 부모 클래스에 강하게 의존하고 있다. 강하게 의존 한다는 것은 부모 클래스의 기능을 전혀 사용하지 않지만 상속을 무조건 받기때문에 강하게 의존 되어 있다고 한다.

추가로 템플릿 메소드 패턴에서는 상속 구조를 사용하기 때문에 별도의 클래스나 익명 내부 클래스를 만들어야한다는 부분도 있다. 

이러한 템플릿 메소드 패턴과 비슷한 역할을 하면서 상속을 제거할 수 있는 전략 패턴이 있다.

728x90

댓글