티스토리 뷰
‘개발자가 반드시 정복해야 할 객체 지향과 디자인 패턴' 책을 보고 정리한 글입니다.
프로그램을 구현하다 보면, 완전히 동일한 절차를 가진 코드를 작성하게 될 때가 있다.
심지어 이 코드들은 절차 중 일부 과정의 구현만 다를 뿐 나머지의 구현은 똑같을 때도 있다.
예를 들어, DB 데이터와 LDAP을 이용해서 인증을 처리하는 클래스는 사용자 정보를 가져오는 부분의 구현만 다를 뿐 인증을 처리하는 과정은 완전히 동일할 수 있다.
실행 과정/단계는 동일한데 각 단계 중 일부의 구현이 다른 경우에 사용할 수 있는 패턴이 템플릿 메서드 패턴이다.
- 실행 과정을 구현한 상위 클래스
- 실행 과정의 일부 단계를 구현한 하위 클래스
상위 클래스는 실행 과정을 구현한 메서드를 제공한다.
이 메서드는 기능을 구현하는데 필요한 각 단계를 정의하며 이 중 일부 단계는 추상 메서드를 호출하는 방식으로 구현된다.
이때 추상 메서드는 구현이 다른 단계에 해당된다.
public abstract class Authenticator {
// 템플릿 메서드
public Auth autheniticate(String id, String pw) {
if (!doAuthenticate(id, pw)) {
throw createException();
}
return createAuth(id);
}
protected abstract boolean doAuthenticate(String id, String pw);
private RuntimeException createException() {
throw new AuthException();
}
protected abstract Auth createAuth(String id);
}
authenticate() 메서드는 DbAuthenticator와 LdapAuthenticator에서 동일했던 실행 과정을 구현하고 있고, 두 클래스에서 차이가 나는 부분은 별도의 추상 메서드로 분리하였다.
예를 들어, id/pw를 이용해서 인증 여부를 확인하는 단계는 doAuthenticate() 추상 메서드로 분리하였고, Auth 객체를 생성하는 단계는 createAuth() 추상 메서드로 분리하였다.
authenticate() 메서드는 모든 하위 타입에 동일하게 적용되는 실행 과정을 제공하기 때문에, 이 메서드를 템플릿 메서드라고 부른다.
Authenticator 클래스를 상속받은 하위 클래스는 authenticate() 메서드에서 호출하는 다른 메서드만 알맞게 재정의해 주면 된다.
예를 들어, LdapAuthenticator 클래스는 다음 코드처럼 구현할 수 있다.
import javax.naming.ldap.LdapContext;
public class LdapAuthenticator extends Authenticator {
@Override
protected boolean doAuthenticate(String id, String pw) {
return ldapClient.authenticate(id, pw);
}
@Override
protected Auth createAuth(String id) {
LdapContext ctx = ldapClient.find(id);
return new Auth(id, ctx.getAttributes("name"));
}
}
LdapAuthenticator 클래스는 이제 전체 실행 과정 구현을 제공하지 않고 일부 과정의 구현만을 제공한다.
전체 실행 과정은 상위 타입인 Authenticator의 authenticate() 메서드에서 제공하게 된다.
템플릿 메서드 패턴을 사용하게 되면, 동일한 실행 과정의 구현을 제공하면서 동시에 하위 타입에서 일부 단계를 구현하도록 할 수 있다.
이는 각 타입에서 코드가 중복되는 것을 방지한다.
템플릿 메서드 패턴을 적용하기 전에 DbAuthenticator와 LdapAuthenticator는 완전히 동일한 구조를 갖고 있었다.
차이점이라면 DB를 사용하느냐 LDAP을 사용하느냐 일뿐, 실행 과정 자체는 완전히 동일했다.
새로운 인증 방식이 추가되더라도 이 과정은 완전히 동일하기 때문에 거의 비슷한 코드가 중복될 것이다.
중복된 코드가 출현한다는 것은 그 만큼 유지 보수를 어렵게 만드는데, 템플릿 메서드 패턴을 사용함으로써 코드 중복 문제를 제거하면서 동시에 코드를 재사용할 수 있게 된다.
상위 클래스가 흐름 제어 주체
템플릿 메서드 패턴의 특징은 하위 클래스가 아닌 상위 클래스에서 흐름 제어를 한다는 것이다.
일반적인 경우 하위 타입이 상위 타입의 기능을 재사용할지 여부를 결정하기 때문에, 흐름 제어를 하위 타입이 하게 된다.
예를 들어, 아래 코드에서 SuperCar 클래스의 turnOn() 메서드는 상위 클래스의 turnOn() 메서드를 재사용할지 여부를 자신이 결정한다.
public class SuperCar extends ZetEngine {
@Override
public void turnOn() {
// 하위 클래스에서 흐름 제어
if (notReady) {
beep();
} else {
super.turnOn();
}
}
}
반면에 템플릿 메서드 패턴에서는 상위 타입의 템플릿 메서드가 모든 실행 흐름을 제어하고, 하위 타입의 메서드는 템플릿 메서드에서 호출되는 구조를 갖게 된다.
템플릿 메서드와 템플릿 메서드에서 호출하는 메서드의 접근 범위는 각각 public과 protected로 설정되어 있다.
템플릿 메서드의 경우 외부에 제공하는 기능에 해당되기 때문에 public 범위를 가져야 한다.
반면에 템플릿 메서드에서만 호출되는 메서드는 public일 필요가 없다.
이 두 메서드는 하위 타입에서 재정의할 수 있어야 하기 때문에 private이 아닌 protected의 범위를 가져야 한다.
템플릿 메서드에서 호출하는 메서드를 추상 메서드로 정의했는데, 기본 구현을 제공하고 하위 클래스에서 알맞게 재정의하도록 구현할 수도 있다.
이 경우 해당 메서드는 기능의 확장 지점으로 사용될 수 있다.
템플릿 메서드와 전략 패턴의 조합
템플릿 메서드와 전략 패턴을 함께 사용하면 상속이 아닌 조립의 방식으로 템플릿 메서드 패턴을 활용할 수 있는데, 대표적인 예가 스프링 프레임워크의 Template으로 끝나는 클래스들이다.
이 클래스들은 템플릿 메서드를 실행할 때, 변경되는 부분을 실행할 객체를 파라미터를 통해서 전달받는 방식으로 구현되어 있다.
예로 트랜잭션 기능을 제공하는 TransactionTemplate 클래스의 execute() 메서드가 있다.
execute() 메서드는 트랜잭션의 시작/커밋/롤백 등의 실행 흐름을 제공하는 템플릿 메서드인데, execute() 메서드는 앞서 살펴본 템플릿 메서드와 다음의 차이점이 있다.
- 앞서 템플릿 메서드가 하위 타입에서 재정의할 메서드를 호출하고 있다면
- TransactionTemplate의 execute() 메서드는 파라미터로 전달받은 action의 메서드를 호출하고 있다.
따라서 TransactionTemplate의 execute() 메서드를 사용하는 코드는 다음과 같이 execute() 메서드를 호출할 때 원하는 기능을 구현한 TransactionCallback 객체를 전달한다.
transactionTemplate.execute(new TransactionCallback<String>() {
public String doInTransaction(TransactionStatus status) {
// 트랜잭션 범위 안에서 실행될 코드
}
});
템플릿 메서드 패턴과 전략 패턴을 조합하게 되면, 상속에 기반을 둔 템플릿 메서드 구현과 비교해서 유연함을 갖는다.
상속을 통한 재사용의 경우 클래스가 불필요하게 증가할 수 있고 런타임에 교체할 수 없는 단점이 있는 반면에 조립/위임을 사용하는 경우에는 런타임에 템플릿 메서드에서 사용할 객체를 교체할 수 있는 장점을 갖게 된다.
하지만, 상속 방식의 경우 훅 메서드를 재정의하는 방법으로 하위 클래스에서 쉽게 확장 기능을 제공할 수 있는 장점이 있는 반면에, 조립/위임 방식에서는 확장 기능을 제공하려면 구현이 다소 복잡해지는 단점이 있다.
'JAVA > 디자인 패턴' 카테고리의 다른 글
어댑터(Adapter) 패턴 (0) | 2022.03.01 |
---|---|
프록시(proxy) 패턴 (0) | 2022.03.01 |
데코레이터(Decorator) 패턴 (0) | 2022.03.01 |
상태(State) 패턴 (0) | 2022.03.01 |
전략(Strategy) 패턴 (0) | 2022.02.23 |
- Total
- Today
- Yesterday
- Spring
- AWS
- 클린 코드
- Java
- 객체지향
- Spring Boot
- 프로그래머스
- Effective Java
- BOJ
- 정규표현식
- 알고리즘
- C++
- Kotlin
- JPA
- kotest
- 클린 아키텍처
- Olympiad
- MSA
- BAEKJOON
- programmers
- 이팩티브 자바
- kkoon9
- node.js
- 이펙티브 자바
- 디자인 패턴
- Algorithm
- 코테
- 디자인패턴
- 백준
- 테라폼
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |