자바로 구현하는 템플릿 메소드 패턴: 공통 알고리즘 추상화와 재사용

자바로 구현하는 템플릿 메소드 패턴

템플릿 메소드 패턴은 객체 지향 프로그래밍에서 자주 사용되는 디자인 패턴 중 하나이다. 이 패턴을 사용하면 공통 알고리즘을 추상화하여 재사용할 수 있으며, 코드의 중복을 줄이고 생산성을 높일 수 있다. 이번 글에서는 자바로 구현하는 템플릿 메소드 패턴에 대해 살펴보고, 이를 활용하여 공통 알고리즘을 추상화하고 재사용하는 방법을 다룰 것이다.

공통 알고리즘 추상화

템플릿 메소드 패턴은 일반적으로 추상 클래스나 인터페이스를 사용하여 구현된다. 이 추상 클래스나 인터페이스는 공통 알고리즘을 추상화한 메소드를 포함하고 있으며, 이 메소드들은 구현체에서 재정의될 수 있는 메소드와 재정의할 수 없는 메소드로 나뉘어진다. 재정의할 수 없는 메소드는 공통 알고리즘의 흐름을 제어하고, 재정의할 수 있는 메소드는 구현체에서 추가적인 동작을 수행할 수 있도록 하는 역할을 한다.

아래는 추상 클래스를 사용한 템플릿 메소드 패턴의 예시이다. 이 예시는 간단한 커피 제조 과정을 추상화한 것이다.

public abstract class CoffeeMaker {

    public final void makeCoffee() {
        boilWater();
        brewCoffee();
        pourInCup();
        addCondiments();
    }

    public void boilWater() {
        System.out.println("Boiling water...");
    }

    public void pourInCup() {
        System.out.println("Pouring into cup...");
    }

    public abstract void brewCoffee();

    public abstract void addCondiments();
}

위 코드에서 makeCoffee() 메소드는 템플릿 메소드이다. 이 메소드는 공통 알고리즘의 흐름을 제어하고, boilWater(), brewCoffee(), pourInCup(), addCondiments() 메소드를 순서대로 호출한다. 이 메소드는 final 키워드로 선언되어 있어서 구현체에서 재정의될 수 없다.

boilWater(), pourInCup() 메소드는 공통 알고리즘의 일부로서 구현체에서 재정의될 수 없다. brewCoffee(), addCondiments() 메소드는 각각 커피를 추출하는 방법과 커피에 추가할 재료를 구현체에서 재정의할 수 있다.

아래는 구현체를 사용한 코드이다. 이 코드는 커피 제조 과정을 구체화한 것이다.

public class AmericanoMaker extends CoffeeMaker {

    @Override
    public void brewCoffee() {
        System.out.println("Brewing americano...");
    }

    @Override
    public void addCondiments() {
        System.out.println("Adding sugar and milk...");
    }
}

위 코드에서 AmericanoMaker 클래스는 CoffeeMaker 클래스를 상속받아 brewCoffee(), addCondiments() 메소드를 재정의한다. 이 클래스는 아메리카노를 만드는 방법에 따라 brewCoffee() 메소드를 구현하고, 설탕과 우유를 추가하는 방법에 따라 addCondiments() 메소드를 구현한다.

재사용을 위한 효과적인 방법

템플릿 메소드 패턴을 사용하여 공통 알고리즘을 추상화하고 재사용하는 것이 가능하다. 이를 효과적으로 수행하기 위해서는 몇 가지 규칙을 따라야 한다.

추상 클래스나 인터페이스를 사용하여 구현

템플릿 메소드 패턴은 보통 추상 클래스나 인터페이스를 사용하여 구현된다. 이렇게 함으로써 공통 알고리즘을 추상화하고, 구현체에서 재정의할 수 있는 메소드를 명시할 수 있다.

공통 알고리즘을 추상화한 메소드는 final 키워드로 선언

공통 알고리즘을 추상화한 메소드는 구현체에서 재정의할 수 없도록 final 키워드로 선언한다. 이렇게 함으로써 공통 알고리즘의 흐름을 제어할 수 있다.

구현체에서 추가적인 동작은 재정의 가능한 메소드를 사용

구현체에서 추가적인 동작을 수행하기 위해서는 재정의 가능한 메소드를 사용한다. 이렇게 함으로써 구현체에서 공통 알고리즘의 흐름을 제어할 수 있고, 새로운 동작을 추가할 수 있다.

추상화한 메소드의 이름을 구체적으로 명시

추상화한 메소드의 이름을 구체적으로 명시하여 구현체에서 쉽게 구현할 수 있도록 한다. 이렇게 함으로써 구현체에서 메소드의 의도를 파악하기 쉬워지며, 코드의 가독성이 높아진다.

상속보다는 조합을 사용

템플릿 메소드 패턴을 사용할 때는 상속보다는 조합을 사용하는 것이 좋다. 상속을 사용하면 코드의 구조가 복잡해지고, 유지보수가 어려워질 수 있다. 조합을 사용하면 필요한 기능을 필요한 만큼만 사용할 수 있으며, 코드의 재사용성을 높일 수 있다.

템플릿 메소드 패턴의 예시와 활용 방법

템플릿 메소드 패턴은 다양한 분야에서 활용될 수 있다. 아래는 템플릿 메소드 패턴을 사용한 예시들이다.

HttpServlet 클래스

자바 웹 프로그래밍에서 HttpServlet 클래스는 템플릿 메소드 패턴을 사용하여 구현되었다. 이 클래스는 HTTP 요청을 처리하는 공통 알고리즘을 추상화한 service() 메소드를 포함하고 있다. 이 메소드는 HTTP 요청의 메소드(GET, POST, PUT, DELETE 등)를 확인하고, 각각의 메소드에 대한 처리를 구현체에서 수행한다.

public abstract class HttpServlet extends GenericServlet {

    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String method = req.getMethod();
        if (method.equals("GET")) {
            doGet(req, resp);
        } else if (method.equals("POST")) {
            doPost(req, resp);
        } else if (method.equals("PUT")) {
            doPut(req, resp);
        } else if (method.equals("DELETE")) {
            doDelete(req, resp);
        }
    }

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
    }

    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
    }

    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
    }

    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
    }
}

위 코드에서 service() 메소드는 공통 알고리즘을 추상화한 메소드이다. 이 메소드는 HTTP 요청의 메소드를 확인하고, 각각의 메소드에 대한 처리를 구현체에서 수행한다. doGet(), doPost(), doPut(), doDelete() 메소드는 각각 GET, POST, PUT, DELETE 메소드에 대한 처리를 수행하는 구현체에서 재정의할 수 있다.

JdbcTemplate 클래스

스프링 프레임워크에서는 JdbcTemplate 클래스를 사용하여 데이터베이스와 연동하는 기능을 제공한다. 이 클래스는 템플릿 메소드 패턴을 사용하여 구현되었다. JdbcTemplate 클래스는 데이터베이스 연동에 필요한 공통 알고리즘을 추상화한 메소드를 제공하고, 구현체에서는 SQL 문장을 작성하여 이를 실행하는 메소드를 구현한다.

public class JdbcTemplate {

    public  List query(String sql, RowMapper rowMapper, Object... args) throws DataAccessException {
        return query(new SimplePreparedStatementCreator(sql, args), new RowCallbackHandlerResultSetExtractor(rowMapper));
    }

    protected  List query(PreparedStatementCreator psc, ResultSetExtractor<List> rse) throws DataAccessException {
        Connection con = DataSourceUtils.getConnection(getDataSource());
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = psc.createPreparedStatement(con);
            rs = ps.executeQuery();
            List results = rse.extractData(rs);
            return results;
        } catch (SQLException ex) {
            throw new DataAccessResourceFailureException("PreparedStatementCallback", ex);
        } finally {
            JdbcUtils.closeResultSet(rs);
            JdbcUtils.closeStatement(ps);
            DataSourceUtils.releaseConnection(con, getDataSource());
        }
    }
}

위 코드에서 query() 메소드는 공통 알고리즘을 추상화한 메소드이다. 이 메소드는 SQL 문장과 매개변수를 받아서 PreparedStatementCreator와 ResultSetExtractor를 생성하고, 이들을 사용하여 데이터베이스와 연동한다. 구현체에서는 RowMapper를 구현하여 각각의 테이블과 매핑하는 방식을 구현할 수 있다.

결론

이번 글에서는 자바로 구현하는 템플릿 메소드 패턴에 대해 살펴보았다. 템플릿 메소드 패턴은 공통 알고리즘을 추상화하여 재사용하고, 코드의 중복을 줄이고 생산성을 높이는 데에 사용된다. 이를 효과적으로 수행하기 위해서는 추상 클래스나 인터페이스를 사용하여 구현하고, 공통 알고리즘을 추상화한 메소드는 final 키워드로 선언하여 구현체에서 재정의할 수 없도록 해야 한다. 또한 구현체에서 추가적인 동작은 재정의 가능한 메소드를 사용하고, 추상화한 메소드의 이름을 구체적으로 명시하여 코드의 가독성을 높이는 것이 좋다. 템플릿 메소드 패턴은 자바 웹 프로그래밍에서 HttpServlet 클래스나 스프링 프레임워크에서 JdbcTemplate 클래스를 구현하는 데에도 활용된다.