Java

템플릿 메서드 패턴을 적용해보자

siwoli 2023. 10. 26. 15:23

템플릿 메서드 패턴이란?

  • 상위 클래스의 템플릿 메서드에서 하위 클래스가 오버라이딩한 메서드를 호출하는 패턴
  • 객체지향 설계 5원칙 중 의존 역전 원칙(Dependency Inversion Principle)이 적용된 디자인 패턴
  1. 상위 클래스에 공통 로직을 수행하는 템플릿 메서드를 둔다.
  2. 템플릿 메서드 안에서 추상 메서드 또는 hook메서드를 호출한다.
  3. 하위 클래스마다 추상 메서드, hook메서드를 오버라이딩해 서로 다른 동작을 하게 한다.
  4. 상위 클래스 타입으로 하위 클래스 객체를 선언한다.
  5. 객체의 템플릿 메서드를 호출한다.

hook메서드

  • 상위클래스에서 디폴트 기능을 정의해두거나 비워뒀다가 하위클래스에서 선택적으로 오버라이드할 수 있도록 만들어둔 메서드
  • abstract 키워드가 없는 메서드

템플릿 메서드

  • 상위클래스에 있으며 기본 알고리즘 골격을 담은 메서드
  • 하위클래스에서 오버라이드하거나 구현할 메서드를 사용함

템플릿 메서드 패턴을 활용하면 객체마다 전체적으로는 같은 동작을 수행할때 부분적으로 다른 동작을 수행할 수 있다.

새로운 부분 동작을 수행하는 객체가 필요할때 부분 동작 구현만 신경쓰면 된다.


템플릿 메서드 패턴의 예시

예를 들어 햄버거를 만든다고 생각해보자.

  1. 햄버거에는 빵 -> 패티 -> 야채 -> 소스 -> 빵 순서로 햄버거를 만드는 템플릿 메서드가 있다.
  2. 햄버거에는 패티를 만드는 추상메서드가 있다.
  3. 치즈버거와 새우버거는 햄버거를 상속받고 패티를 만드는 메서드를 오버라이딩한다.
    3-1. 치즈버거는 소고기 패티를 만드는 메서드
    3-2. 새우버거는 새우 패티를 만드는 메서드
  4. 햄버거 타입으로 치즈버거 객체를 선언한다.
  5. 치즈버거의 햄버거 만드는 메서드를 호출하면 빵 -> 소고기패티 -> 야채 -> 소스 -> 빵 순서로 햄버거를 만든다.

템플릿 메서드 패턴 적용하기

우테코 프리코스 1주차에서 숫자를 생성하는 기능에 템플릿 메서드 패턴을 적용했다.

  1. NumbersGenerator에서 generate()createNumbers()의 반환값을 Numbers선언부에 넘겨줘 컬렉션 값을 반환한다.
  2. public abstract class NumbersGenerator { public List<Integer> generate() { Numbers numbers = new Numbers(createNumbers()); return numbers.getNumbers(); } abstract List<Integer> createNumbers(); }
  3. ComputerNumbersGenerator와 UserNumbersGenerator는 createNumbers()를 오버라이딩해 각자만의 방식으로 생성한 List를 반환한다.
public class ComputerNumbersGenerator extends NumbersGenerator{

    public List<Integer> createNumbers() {
        List<Integer> computerNumbers = new ArrayList<>();
        while (computerNumbers.size() < Constants.BASEBALL_NUMBER_SIZE) {
            int randomNumber = Randoms.pickNumberInRange(Constants.COMPUTER_NUMBER_START, Constants.COMPUTER_NUMBER_END);
            addRandomNumber(computerNumbers, randomNumber);
        }
        return computerNumbers;
    }

    private void addRandomNumber(List<Integer> computerNumbers, int randomNumber) {
        if (!computerNumbers.contains(randomNumber)) {
            computerNumbers.add(randomNumber);
        }
    }
}
public class UserNumbersGenerator extends NumbersGenerator{

    public List<Integer> createNumbers() {
        List<Integer> userNumbers = new ArrayList<>();
        System.out.print("숫자를 입력해주세요 : ");
        String userInput = readLine();
        for (int i = 0; i < userInput.length(); i++) {
            String str = userInput.substring(i, i + 1);
            userNumbers.add(Integer.parseInt(str));
        }
        return userNumbers;
    }
}

4. 각 객체를 사용한다.
ComputerNumbersGenerator는 생성자의 파라미터로 받도록 했다.

public class UnitOfGame extends Game {
 private final List<Integer> computerNumbers;

 public UnitOfGame(NumbersGenerator numbersGenerator) {
     this.computerNumbers = numbersGenerator.generate();
 }
...
public class UnitOfGameFactory implements GameFactory {
    @Override
    public UnitOfGame create() {
        return new UnitOfGame(new ComputerNumbersGenerator());
    }
}

UserNumbersGeneratorUnitOfGame의 메서드에서 객체를 만들어 사용했다.

...
//UserNumbersGenerator
NumbersGenerator numbersGenerator = new UserNumbersGenerator();
List<Integer> userNumbers = numbersGenerator.generate();
...

이렇게 함으로써 같은 타입의 같은 메서드를 호출해도 다른 숫자를 받을 수 있었다.
그리고 숫자를 생성하는 방식이 바뀌면, 각 하위 클래스들의 createNumbers()만 수정하면 된다.


템플릿 메서드 패턴의 대략적인 개념만 알고 있었는데
직접 적용해보니 어떻게 설계하고 사용해야되는지 알게 되었다!