Spring Boot/디자인패턴

[Spring] Spring에서 사용되는 템플릿 콜백 패턴

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

콜백 정의

프로그래밍에서 콜백 또는 콜애프터 함수는 다른 코드의 인수로서 넘겨주는 실행 가능한 코드를 말한다.

콜백을 넘겨받는 코드는 이 콜백을 필요에 따라 즉시 실행할 수도 있고, 아니면 나중에 실행할 수도 있다.

 

템플릿 콜백 패턴

템플릿 콜백 패턴은 전략 패턴의 변형된 형태입니다. 전략패턴은 코드가 변화되는 부분을 매번 클래스로 만들고 외부에서 구체 클래스를 주입해 주어야 한다. 반면 템플릿 콜백 패턴은 변화되는 부분을 독립된 클래스를 만드는 것이 아니라 익명 내부 클래스를 생성하여 이를 활용하므로 주입이 필요하지 않다.

그렇기 때문에 템플릿은 정해져있는 틀, 콜백은 인수로 넘겨주는 실행 가능한 코드.

이 두개를 합쳐 템플릿 콜백 패턴이라 한다.

템플릿 콜백 패턴은 GOF 패턴은 아니고 스프링 내부에서 이런 방식을 자주 사용하기 때문에 스프링에서만 이렇게 부른다고 합니다.

 

 

전략 패턴 예제 코드

축구를 한다고 가정한다. 선수의 포지션은 감독이 정해주고 이때 선수는 공격수, 미드필더, 수비수의 포지션을 배정받을 수 있지만, 감독이 포지션을 골라주어야 시합을 나갈 수 있습니다.

 

[Spring] Spring에서 사용되는 템플릿 콜백 패턴 - 전략 패턴 예제 코드

 

포지션을 고르는 전략을 Strategy, 전략을 수행하는 FootballPlayer(선수), 구체 클래스를 주입하는 FootballManager(감독)으로 구성되어 있습니다.

 

public interface Strategy {
    public abstract void choosePosition();
}

public class Defender implements Strategy {
    @Override
    public void choosePosition() {
        System.out.println("수비수로 선발되었습니다.");
    }
}

public class Midfielder implements Strategy {
    @Override
    public void choosePosition() {
        System.out.println("미드필더로 선발되었습니다.");
    }
}

public class Forward implements Strategy {
    @Override
    public void choosePosition() {
        System.out.println("공격수로 선발되었습니다.");
    }
}

public class FootballPlayer {
    public void beSelected(Strategy strategy) {
        System.out.println("감독이 포지션을 선택합니다.");
        strategy.choosePosition();
        System.out.println("경기를 뜁니다.");
    }
}

public class FootballManager {
    public void execute() {
        FootballPlayer footballPlayer = new FootballPlayer();

        Defender defender = new Defender();
        Midfielder midfielder = new Midfielder();
        Forward forward = new Forward();

        footballPlayer.beSelected(defender);
        footballPlayer.beSelected(midfielder);
        footballPlayer.beSelected(forward);
    }
}

 

전략 패턴은 런타임 도중 객체를 주입하는 DI를 사용함으로써 구현하게 됩니다.

 

템플릿 콜백 패턴 예제 코드

템플릿 콜백 패턴은 전략 패턴과 달리 구현 클래스를 미리 만들어 놓지 않으며 익명 내부 클래스를 사용한다.

그레서 전략 패턴 코드를 다음과 같이 바꿀 수 있다.

 

public class FootballManager {
    public void execute() {
        FootballPlayer footballPlayer = new FootballPlayer();

        footballPlayer.beSelected(new Strategy() {
            @Override
            public void choosePosition() {
                System.out.println("수비수로 선발되었습니다.");
            }
        });

        footballPlayer.beSelected(new Strategy() {
            @Override
            public void choosePosition() {
                System.out.println("미드필더로 선발되었습니다.");
            }
        });

        footballPlayer.beSelected(new Strategy() {
            @Override
            public void choosePosition() {
                System.out.println("공격수로 선발되었습니다.");
            }
        });
    }
}

 

이렇게 호출하는 쪽에서 익명 내부 클래스를 통해 전략을 호출 시점에서 정해주며 로직을 수행할 수 있습니다.

하지만 매번 익명 내부 클래스를 만드는 것이 번거로우므로 이를 FootballPlayer 클래스 안에 넣어 보겠습니다.

 

 

public class FootballPlayer {

    public void beSelected(String position) {
        System.out.println("감독이 포지션을 선택합니다.");
        strategy(position).choosePosition();
        System.out.println("경기를 뜁니다.");
    }

    private Strategy strategy(String position) {
        return new Strategy() {
            @Override
            public void choosePosition() {
                System.out.println(position + "로 선발되었습니다.");
            }
        };
    }
}


public class FootballManager {
    public void execute() {
        FootballPlayer footballPlayer = new FootballPlayer();

        footballPlayer.beSelected("수비수");
        footballPlayer.beSelected("미드필더");
        footballPlayer.beSelected("공격수");
    }
}

 

익명 클래스를 FootballPlayer 객체 내부로 옮기고 변하는 최소한의 부분만 클라이언트에서 주입하도록 수정함으로써 코드가 간결해진 것을 확인할 수 있다.

 

 

 

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

  • 장점
    • 전략 패턴은 객체와 전략 객체 간의 의존성을 설정해 주는 팩토리 객체가 필요한데, 템플릿 콜백 패턴은 팩토리 객체 없이 해당 객체를 사용하는 메서드에서 인터페이스의 전략을 선택한다. 그래서 수동 DI, 메서드 수준의 DI라고 부른다. 이를 통해 모든 전략마다 팩토리 객체를 일일이 만들 필요가 없다.
    • 외부에서 어떤 전략을 사용하는지 감추고 중요한 부분에 집중할 수 있다.
  • 단점
    • 알고리즘 구조가 복잡할 수록 템플릿을 유지하기 어려워진다.

 

Spring에서 템플릿 콜백 패턴을 적용한 사례

Spring에서는 xxxTemplate를 만나면 템플릿 콜백 패턴을 적용한 사례로 이해하면 된다. 대표적으로 JdbcTemplate에서 사용되고 있다.

@Override
	public int update(final String sql) throws DataAccessException {
		Assert.notNull(sql, "SQL must not be null");
		if (logger.isDebugEnabled()) {
			logger.debug("Executing SQL update [" + sql + "]");
		}

		/**
		 * Callback to execute the update statement.
		 */
		class UpdateStatementCallback implements StatementCallback<Integer>, SqlProvider {
			@Override
			public Integer doInStatement(Statement stmt) throws SQLException {
				int rows = stmt.executeUpdate(sql);
				if (logger.isTraceEnabled()) {
					logger.trace("SQL update affected " + rows + " rows");
				}
				return rows;
			}
			@Override
			public String getSql() {
				return sql;
			}
		}

		return updateCount(execute(new UpdateStatementCallback(), true));
	}

 

jdbcTemplate의 update 메소드를 보면 내부 클래스로 정의된 콜백이 호출되는 모습을 확인할 수 있다.

 

클라이언트에서 sql문을 jdbcTempalte의 CRUD 메서드를 호출하고 update() 메서드 내부의 정의된 익명 클래스(콜백 객체)를 사용하고 있다.

728x90

댓글