본문 바로가기
프로그래밍/디자인 패턴

[Spring & Design Pattern] Spring에서 발견한 디자인패턴_template method pattern

by 사바라다 2019. 9. 4.

Spring Boot로 개발을 하다보니 그 중심이 되는 DispatcherServlet이 궁금해 졌었습니다. 그래서 이전에 DispatcherServlet의 전체적인 프로세스를 간략하게 정리한 [Spring]DispatcherServlet Code로 분석하기 - reqeust & response process 포스팅을 진행 했었습니다. DisaptcherServlet을 분석하던 중 다양한 Design Pattern이 사용됬다는 것을 확인했고 이를 포스팅 하고 싶어졌습니다.

오늘은 DispatcherServlet에서 사용된 디자인 패턴 중 하나인 Template method Pattern에 대해서 알아보도록 하겠습니다.

template method pattern

서로 다른 유사한 컴포넌트가 있을때, 공통 인터페이스나 구현을 재사용하지 않을 수 있습니다. 구성요소의 대부분을 동일하게 가져가지만 일부만 다르게 가져갈때 중복을 최소화 하기위해서 우리는 template method pattern을 사용합니다.

예를 들어봅시다.

위와 같이 작업자가 여러명있습니다. 그리고 위의 클래스는 사람들의 하루일과를 나타내는 클래스입니다. 대부분은 상당히 유사한 구조를 가집니다. 하지만 work에 대해서는 각자 하는 일이 분명히 다릅니다. 이것을 Class구조로 나타내면 위와 같이 그려지겠지요. 여기서 Worker Class의 DailyRoutine Class를 보겠습니다. DailyRoutine Method는 하루 일과를 한번에 나타내는 Method로 아래와 같은 flow를 가집니다.

getUp -> eatBreakfast -> goToWork -> work -> returnTOHome -> relax -> sleep

이경우 work, relax등 일부만 다를 수 있습니다. 이런경우 구현되어 있는 일부 method만 subClass에서 실행할 수 있다면 완벽해지겠구나라는 것을 알 수 있습니다. 이때 사용할 수 있는 Design Pattern이 Template Method Pattern입니다. 구조는 아래와 같이 됩니다. 그러면 한번 예제를 만들어 보도록 하겠습니다.

template method pattern을 코드로 보기

만들어볼 예제는 위에 있는 이미지의 lifeCycle에 대한 Class입니다.

1. Worker

package com.main;

public abstract class Worker {

    public void DailyRoutine()
    {
        getUp();
        eatBreakfast();
        goToWork();
        work();
        returnToHome();
        relax();
        sleep();
    }

    private void getUp()
    {
        System.out.println("getUp!");
    }

    private void eatBreakfast()
    {
        System.out.println("getBreakfast");
    }

    private void goToWork()
    {
        System.out.println("goToWork");
    }

    protected abstract void work();

    private void returnToHome()
    {
        System.out.println("returnToHome");
    }

    protected abstract void relax();

    private void sleep()
    {
        System.out.println("sleep");
    }
}

2. FireFighter

package com.main;

public class FireFighter extends Worker{

    @Override
    protected void work() {
        System.out.println("FireFighter`s work");
    }

    @Override
    protected void relax() {
        System.out.println("FireFighter`s relax");
    }
}

3. Postman

package com.main;

public class Postman extends Worker {

    @Override
    protected void work() {
        System.out.println("Postman`s Work");
    }

    @Override
    protected void relax() {
        System.out.println("Postman`s relax");
    }

}
  1. Main
package com.main;

public class Main {

    public static void main(String[] args) {

        Worker fireFighter = new FireFighter();
        Worker postMan = new Postman();

        fireFighter.DailyRoutine();
        System.out.println("--------------------------------");
        postMan.DailyRoutine();
    }
}
  1. 출력
getUp!
getBreakfast
goToWork
FireFighter`s work
returnToHome
FireFighter`s relax
sleep
--------------------------------
getUp!
getBreakfast
goToWork
Postman`s Work
returnToHome
Postman`s relax
sleep

Process finished with exit code 0

위의 구조에 따라 Java코드를 작성하고 테스트 해본 결과입니다. 1번의 Worker를 보시면 공통으로 사용되는 부분은 자체적으로 구현을 하였고, 다르게 구현되어야 할 메서드인 work()relax()는 SubClass에게 위임하기위해서 abstract로 만들었습니다. 그리고 subClass인 2번의 FireFighter와 3번의 Postman을 보시면 해당하는 method를 상속 받아서, 구현하고 있습니다. 이렇게 구현을 한 후 Client(사용하는 측)인 4. Main을 보시면 FireFighterPostman을 Instance로 만든 부분과 DailyRoutine이라는 메서드를 각각 호출한 것을 볼 수 있습니다. 5번의 출력결과를 보시면 work(), relax() method가 subClass의 method가 호출된것을 보실 수 있습니다.

장점과 단점

그렇다면 이런 template method pattern의 장점과 단점이 뭘까요?

 

장점

  • 코드중복을 크게 줄일수 있다.
  • SubClass 객체의 롤을 최대한 줄임으로서 핵심로직에 집중한다.
  • 쉽게 자식 객체를 추가, 확장해 나갈수 있다.

단점

  • 구현 클래스가 구현해야 하는 abstact method가 너무 많으면 관리가 곤란하다.
  • 반드시 추상 클래스의 템플릿 메서드에서 구현클래스의 메서드를 부르는 식으로 로직을 구성해야 한다.(상위->하위) 자칫하면 혼선이 생기기 쉽다.

Spring에서는 어디에 사용하고 있을까?

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

 

 

여기 DispatcherServletdoService() 라는 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의 로직을 타다 soService(request, response)를 만나면 DispatcherSerlvet의 로직을 타게 되는것입니다.

 

마무리

오늘은 이렇게 Spring에서 사용하고 있는 tmeplate method pattern에 대해서 자세하게 알아보았고 Spring에서는 어디에 쓰이는지 알아보았습니다.

감사합니다.

참조

https://sourcemaking.com/design_patterns/template_method

http://blog.naver.com/PostView.nhn?blogId=2feelus&logNo=220669520535&redirect=Dlog&widgetTypeCall=true

댓글