Developer Log
  • Wiki
  • Books
    • 리눅스 시스템 프로그래밍
      • 핵심 개념 소개
        • 시스템 프로그래밍
        • API와 ABI
        • 리눅스 프로그래밍의 개념
          • 파일과 파일시스템
          • 프로세스
          • 사용자와 그룹
          • 권한
          • 시그널
          • 프로세스간 통신
          • 에러 처리
    • 데이터 베이스 첫걸음
      • 데이터베이스란
        • 데이터베이스의 역할을 생각해 보자
          • 우리와 데이터베이스의 관계
          • 데이터베이스의 기본 기능
          • 데이터베이스 종류
      • 관계형 데이터베이스란
        • 대표적인 DBMS를 알아보자
          • 관계형 데이터베이스란
          • SQL 기초 지식
          • 관계형 데이터베이스를 다루기 위한 사전 지식
      • 데이터베이스에 얽힌 돈 이야기
        • 초기비용과 운영비용을 생각하자
      • 데이터베이스와 아키텍처 구성
        • 다중화에 대해 생각해보자
          • 아키텍처란
          • 데이터베이스의 아키텍처
            • 역사와 개요
              • Stand-alone
              • 클라이언트/서버
              • Web 3계층
            • 가용성과 확장성의 확보
          • DB 서버의 다중화
            • 클러스터링
            • 리플리케이션
          • 성능을 추구하기 위한 다중화 - Shared Nothing
          • 적합한 아키텍처를 설계하기 위해
      • DBMS를 조작할 때 필요한 기본 지식
        • MySQL 설치해보자
        • MySQL과 커넥션 만들기, 데이터베이스에 전화걸기
        • SQL과 관리 명령의 차이
        • 관계형 데이터베이스의 계층
      • SQL 문의 기본
        • SELECT 문으로 테이블 내용을 살펴보자
        • SELECT 문을 응용해보자
        • 데이터를 갱신, 삽입, 제거해보자
        • 뷰를 작성하고 복수 테이블에서 선택해보자
      • 트랜잭션과 동시성 제어
      • 테이블 설계의 기초
      • 백업과 복구
      • 성능을 생각하자
    • 개발자가 반드시 정복해야 할 객체 지향과 디자인 패턴
      • 객체 지향
        • 들어가기
        • 객체 지향
        • 다형성과 추상 타입
        • 재사용: 상속보단 조립
      • 설계 원칙 / DI와 서비스 로케이터
        • 설계 원칙: SOLID
          • 단일 책임 원칙(Single Responsibility Principle)
          • 개방 폐쇄 원칙(Open - Closed Principle)
          • 리스코프 치환 원칙(Liskov Substitution Principle)
          • 인터페이스 분리 원칙(Interface Segregation Principle)
          • 의존 역전 원칙(Dependency Inversion Principle)
          • SOLID 정리
        • DI(Dependency Injection)와 서비스 로케이터
      • 주요 디자인 패턴
        • 디지인 패턴이란?
        • 전략(Strategy) 패턴
        • 템플릿 메서드(Template Method) 패턴
        • 상태(State) 패턴
        • 데코레이터(Decorator) 패턴
        • 프록시(Proxy) 패턴
        • 어댑터(Adapter) 패턴
        • 옵저버(Observer) 패턴
        • 미디에이터(Mediator) 패턴
        • 파사드(Facade) 패턴
        • 컴포지트(Composite) 패턴
        • 널(Null) 객체 패턴
        • 팩토리 메서드 패턴
        • 커맨드 패턴
        • 추상 팩토리 패턴
    • 테스트 주도 개발
      • 1부
        • 다중 통화를 지원하는 Money 객체
        • 타락한 객체
        • 모두를 위한 평등
        • 프라이버시
        • 솔직히 말하자면
        • 돌아온 '모두를 위한 평등'
        • 사과와 오렌지
Powered by GitBook
On this page
  • 상위 객체가 흐름 제어 주체
  • 템플릿 메서드와 전략 패턴의 조합

Was this helpful?

  1. Books
  2. 개발자가 반드시 정복해야 할 객체 지향과 디자인 패턴
  3. 주요 디자인 패턴

템플릿 메서드(Template Method) 패턴

프로그램을 구현하다 보면, 완전히 동일한 절차를 가진 코드를 작성하게 될 경우가 있다. 또한, 일부 구현만 다를 뿐 나머지의 구현은 똑같은 경우도 있다. 예를 들어 아래와 같이, DB 데이터와 LDAP을 이용해서 인증을 처리하는 객체는 사용자 정보를 가져오는 구현만 다를 뿐 인증을 처리하는 과정은 완전히 동일할 수 있다.

public class DbAthenticator {
    private UserDao userDao;

    public DbAthenticator(UserDao userDao) {
        this.userDao = userDao;

    }

    public Auth authenticate(String id, String pw) throws Exception {
        User user = userDao.selectById(id);
        boolean auth = user.equalPassword(pw);
        if(!auth) {
            throw new Exception("has not auth");

        }

        return new Auth(id, pw);

    }

}
public class LdapAuthenticator {
    private LdapClient ldapClient;

    public LdapAuthenticator(LdapClient ldapClient) {
        this.ldapClient = ldapClient;

    }

    public Auth authenticate(String id, String pw) throws Exception {
        boolean auth = ldapClient.authenticate(id, pw);
        if(!auth) {
            throw new Exception("has not auth");

        }

        return new Auth(id, pw);

    }

}

이렇듯, 실행 과정은 동일하지만 일부 구현이 다른 경우에 사용할 수 있는 패턴이 템플릿 메서드 패턴이다. 템플릿 메서드 패턴은 두 가지로 구성된다.

  • 실행 과정을 구현한 상위 객체

  • 실행 과정의 일부 단계를 구현한 하위 객체

상위 객체는 실행 과정을 구현한 메서드를 제공한다. 이 메서드는 기능을 구현하는데 필요한 각 단계를 정의하며, 이 중 일부 단계는 추상 메서드를 호출하는 방식으로 구현된다. 앞서 보인 예제에 템플릿 메서드를 적용해보면 상위 객체는 다음과 같이 구성할 수 있다.

public abstract class Authenticator {
    public Auth authenticate(String id, String pw) throws Exception {
        if(!doAuthenticate(id, pw)) {
            throw new Exception("has not authentication");

        }

        return createAuth(id);

    }

    protected abstract boolean doAuthenticate(String id, String pw);

    protected abstract Auth createAuth(String id);

}

authenticate() 메서드는 DbAthenticator와 LdapAuthenticator에서 동일했던 실행 과정을 구현하고 있고, 두 객체에서 차이가 나는 부분은 별도의 추상 메서드로 분리하였다.

id/pw를 이용하여 인증 여부를 확인하는 단계는 doAuthenticate() 추상 메서드로 분리하였고, Auth 객체를 생성하는 단계는 createAuth() 추상 메서드로 분리하였다. authenticate() 메서드는 모든 하위 타입에 동일하게 적용되는 실행 과정을 제공하기 때문에 템플릿 메서드(Template Method) 라고 부른다.

Authenticator 객체를 상속하는 하위 객체는 authenticate() 메서드에서 호출하는 추상 메서드만 자신의 상황에 알맞게 재정의하면 된다.

public class DbAthenticator extends Authenticator{
    private UserDao userDao;

    public DbAthenticator(UserDao userDao) {
        this.userDao = userDao;

    }

    @Override
    protected boolean doAuthenticate(String id, String pw) {
        User user = userDao.selectById(id);
        return user.equalPassword(pw);

    }

    @Override
    protected Auth createAuth(String id, String pw) {
        return new Auth(id, pw);

    }

}

결과적으로, DbAuthenticator 객체는 전체 실행 과정을 제공하지 않고, 일부 과정의 구현만을 제공한다. 전체 실행 과정은 상위 타입인 Authenticator인 authenticate() 템플릿 메서드에서 제공하게 된다.

상위 타입에서의 템플릿 메서드는 동일한 실행 과정의 구현을 제공하고, 각 하위 타입마다 다른 구현 부분은 추상 메서드로 분리하여 코드 중복을 방지할 수 있다. 중복된 코드가 출현한다는 것은 그 만큼 유지 보수를 어렵게 만드는데, 템플릿 메서드 패턴는 코드 중복을 제거하고 코드 재사용성을 향상시킨다.

상위 객체가 흐름 제어 주체

템플릿 메서드 패턴의 특징은 하위 객체가 아니라 상위 객체에서 흐름 제어를 한다는 것이다. 일반적으로 하위 타입이 상위 타입 메서드에 대한 재사용 여부를 결정하기 때문에, 하위 타입애서 흐름 제어를 하게 된다. 반면에 템플릿 메서드 패턴에서는 상위 타입의 템플릿 메서드가 모든 실행 흐름을 제어하고, 하위 타입의 메서드는 템플릿 메서드에서 호출되는 구조를 갖게 된다.

템플릿 메서드는 외부에 제공하는 기능에 해당되기 때문에 public 접근 범위를 가진다. 반면에, 하위 타입에서 재정의하는 추상 메서드는 템플릿 메서드에서만 호출되기 때문에 public 보단 protected 접근 범위가 적합하다.

앞서 보인 예제에서는 템플릿 메서드에서 호출하는 메서드를 추상 메서드로 정의하였는데, 기본 구현을 제공하고 하위 객체에서 알맞게 재정의하도록 구현할 수도 있다. 이러한 경우, 해당 메서드는 기능의 확장 지점으로 사용할 수 있다. 예를 들어, 아래의 validate() 메서드는 하위 객체에서 재정의 하지 않는다면 항상 true 값을 반환할 것이다.

public abstract class Authenticator {
    public Auth authenticate(String id, String pw) throws Exception {
        if(!validate(id, pw)) {
            throw new Exception("id, pw is Invalid");

        }

        if(!doAuthenticate(id, pw)) {
            throw new Exception("has not authentication");

        }

        return createAuth(id, pw);

    }

    protected abstract boolean doAuthenticate(String id, String pw);

    protected abstract Auth createAuth(String id, String pw);

    protected boolean validate(String id, String pw) {
        return true;

    }

}

Authenticator 상속한 하위 객체는 doAuthenticate(), createAuth() 추상 메서드는 반드시 구현해야 하지만, validate() 메서드는 필요한 경우에만 구현해주면 된다. 즉, validate() 메서드는 상위 객체 입장에서는 제어 대상이 되는 확장 Point가 되며, 하위 객체 입장에서는 자신의 상황에 맞는 확장 기능을 구현할 위치가 된다. 위 예에서 하위 객체에서 인자 유효성 검사가 필요할 경우, validate() 메서드는 기능 확장의 Point가 된다.

public class DbAthenticator extends Authenticator{

    ...

    @Override
    protected boolean validate(String id, String pw) {
        return id.startsWith("_") && pw.endsWith("_");

    }

}

Hook 메서드

상위 객체에서 실행 시점이 제어되고, 기본 구현을 제공하면서, 하위 객체에서 알맞게 확장할 수있는 메서드를 훅(Hook) 메서드라고 부른다.

템플릿 메서드와 전략 패턴의 조합

템플릿 메서드와 전략 패턴을 함께 사용하면 상속이 아닌 조립 방식으로 템플릿 메서드 패턴을 활용할 수 있는데, 대표적으로 스프링 프레임워크의 Template으로 끝나는 클래스가 있다. 해당 클래스들은 템플릿 메서드를 실행할 때, 변경되는 부분을 실행할 객체를 파라미터로 전달받는 방식으로 구현되어 있다. 예를 들어, JDBC 기능을 제공하는 JdbcTemplate 객체의 execute() 메서드는 아래와 같이 구현되어 있다.

public <T> T execute(ConnectionCallback<T> action) throws DataAccessException {
      Assert.notNull(action, "Callback object must not be null");
      Connection con = DataSourceUtils.getConnection(obtainDataSource());

      try {
            Connection conToUse = createConnectionProxy(con);
            return action.doInConnection(conToUse);

        } catch (SQLException ex) {
            // 생략

        } finally {
            // 생략

        }

    }

execute() 메서드는 사용 가능한 Connection을 찾아서 데이터 액세스 작업을 실행하는 템플릿 메서드인데, 앞서 살펴본 템플릿 메서드와 몇 가지 차이점이 있다.

  • 앞서 템플릿 메서드는 추상 메서드를 호출한다.

  • JdbcTemplate 객체의 execute() 메서드는 인자로 전달받은 ConnectionCallback 타입 객체의 메서드를 호출하고 있다.

따라서, JdbcTemplate의 execute() 메서드를 사용하는 Client는 원하는 기능을 구현한 ConnectionCallback 객체를 조립 방식으로 전달한다. 만약 객체를 전달하지 않는다면 해당 템플릿 메서드는 정상적으로 동작할 수 없기 때문에 스프링 프레임워크에서도 Assert로 객체를 전달하도록 요구한다.

jdbcTemplate.execute(new ConnectionCallback<Object>() {
      @Override
      public Object doInConnection(Connection con) throws SQLException, DataAccessException {
                // 커넥션 범위 안에서 실행할 코드

      }

});

템플릿 메서드 패턴과 전략 패턴을 조합하게 되면, 상속에 기반을 둔 템플릿 메서드 구현과 비교해서 유연할을 갖는다. 상속으로 재사용할 경우 클래스가 불필요하게 증가할 수 있고 런타임에 교체할 수 없는 단점이 있다. 반면에, 조립/위임 방식의 경우에는 런타임에 템플릿 메서드에서 사용할 객체를 교체할 수 있다는 장점을 갖는다.

상속 방식은 Hook 메서드를 하위 객체에 재정의하여 쉽게 확장할 수 있지만, 조립/위임 방식은 확장 기능을 제공하려면 다소 복잡해지는 단점이 있다.

Previous전략(Strategy) 패턴Next상태(State) 패턴

Last updated 5 years ago

Was this helpful?