템플릿 메서드 패턴 예제 2 - Authenticator (Local, SNS)

리팩토링 전

public class LocalAuthenticator {
		public Auth authenticate(String id, String pw) {
				// 사용자 정보로 인증 확인
				User user = userDao.selectById(id);
				boolean auth = user.equalPassword(pw);
				
				// 인증 실패 시 예외 발생
				if (!auth) {
						throw new AuthException();
				}

				// 인증 성공 시, 인증 정보 제공
				return new Auth(id, user.getName);
		}
}
public class SnsAuthenticator {
		public Auth authenticate(String id, String pw) {
				// 사용자 정보로 인증 확인
				boolean auth = snsClient.authenticate(id, pw);
				
				// 인증 실패 시 예외 발생
				if (!auth) {
						throw new AuthException();
				}
			
				// 인증 성공 시, 인증 정보 제공
				return new Auth(id, snsClient.find(id));
		}
}

템플릿 메서드 패턴 적용

Step 1) 로직이 ‘다른 부분’과 ‘동일한 부분을 각각 메서드로 추출

public class LocalAuthenticator {
		public Auth authenticate(String id, String pw) {
				// 사용자 정보로 인증 확인
				~~User user = userDao.selectById(id);
				boolean auth = user.equalPassword(pw);~~
				boolean auth = **doAuthenticate(id, pw);**
				
				// 인증 실패 시 예외 발생
				if (!auth) {
						throw new AuthException();
				}

				// 인증 성공 시, 인증 정보 제공
				~~return new Auth(id, user.getName);~~
				**return createAuth(id);**
		}

		**private boolean doAuthenticate(String id, String pw) {**
				User user = userDao.selectById(id);
				return user.equalPassword(pw);
		**}

		private Auth createAuth(String id) {
				User user = userDao.selectById(id);
				return new Auth(id, user.getName);
		}**
}
public class SnsAuthenticator {
		public Auth authenticate(String id, String pw) {
				// 사용자 정보로 인증 확인
				~~boolean auth = snsClient.authenticate(id, pw);~~
				boolean auth = **doAuthenticate(id, pw);**			
	
				// 인증 실패 시 예외 발생
				if (!auth) {
						throw new AuthException();
				}
			
				// 인증 성공 시, 인증 정보 제공
				~~return new Auth(id, snsClient.find(id));~~
				return **createAuth(id);**
		}

		**private boolean doAuthenticate(String id, String pw) {**
				return snsClient.authenticate(id, pw);
		**}

		private Auth createAuth(String id) {
				return snsClient.authenticate(id, pw);
		}**
}

Step2) 중복된 로직을 합치기 위해 추상화한 부모 클래스 생성

public abstract class Authenticator {
		**// 템플릿 메서드 (흐름 제어를 갖고 있는 메서드)**
		public Auth authenticate(String id, String pw) {
				// 사용자 정보로 인증 확인
				boolean auth = **doAuthenticate(id, pw);**
				
				// 인증 실패 시 예외 발생 (그대로)
				if (!auth) {
						throw new AuthException();
				}

				// 인증 성공 시, 인증 정보 제공
				return **createAuth(id);**
		}
		
		**abstract boolean doAuthenticate(String id, String pw);

		abstract Auth createAuth(String id);**
}

Step3) 부모 클래스에 맞게 자식 클래스 구현

  1. 각 자식 클래스들은 부모 클래스를 상속 받아, 추상 메서드로 분리한 부분을 @Override를 통해 각각의 로직을 작성하면 된다.
public class LocalAuthenticator extends Authenticator {

		public Auth authenticate(String id, String pw) {
				@Override
				boolean doAuthenticate(String id, String pw) {
						User user = userDao.selectById(id);
						return user.equalPassword(pw);
				}
				
				@Override
				Auth createAuth(String id) {
						User user = userDao.selectById(id);
						return new Auth(id, user.getName(id));
				}
		}
}
public class SnsAuthenticator extends Authenticator {
		public Auth authenticate(String id, String pw) {
				@Override
				boolean doAuthenticate(String id, String pw) {
						boolean auth = snsClient.authenticate(id, pw);
						return auth;
				}
				
				@Override
				Auth createAuth(String id) {
						return new Auth(id, snsClient.find(id));
				}
		}
}

References