Java/Design Pattern

[디자인 패턴] 행동 패턴 - 템플릿 메소드 패턴(Template Method Pattern)

민돌v 2023. 4. 24. 11:03
728x90
(인프런) 코딩으로 학습하는 GoF의 디자인 패턴 - 백기선, 강의를 보고 정리한 글입니다.
코드는 GitHub 에 있습니다

 

#1. 객체 생성 관련 패턴

#2. 구조 관련 패턴

 

#3. 행동 관련 패턴

 

 


✔️ 템플릿 메소드 패턴(Template Method Pattern)

알고리즘의 구조를 서브 클래스가 확장할 수 있도록 템플릿으로 제공하여 구체적인 방법을 서브클래스에게 위임하는 패턴입니다.
  • 추상클래스는 템플릿을 제공하고 하위 클래스는 구체적인 알고리즘을 제공합니다.
  • 전체적인 로직 (큰 틀)은 상위 클래스에 정의되어있지만 → 세부적인 구현은 하위 클래스에 맡겨져 있는 구조입니다.
  • 👏🏻 즉, 상위 클래스에서 처리의 흐름을 제어하고, 하위클래스에서 처리의 내용을 구체화하는  디자인 패턴입니다. 

 

→ 추상클래스의 상속을 사용

 

 


✔️  템플릿 메소드 패턴 예시

  • 하나의 파일을 읽어와, 사칙연산을 수행하는 코드를 예시로 들겠습니다. 

 

템플릿 메소드 패턴 적용 전

먼저 더하기를 구현한 코드 입니다.

Client

  • 클라이언트에서 파일을 읽고, 더하기 연산을 수행하는 process 메소드를 실행시켜 결과를 얻습니다.
public class Client {

    public static void main(String[] args) {
        FileProcessor fileProcessor = new FileProcessor("number.txt");
        int result = fileProcessor.process();
        System.out.println(result);
    }
}

 

FileProcessor

  • txt 파일이 있다고 가정하고, 파일안의 숫자를 하나씩 불러와 + 기를 연산하고 있습니다.
public class FileProcessor {

    private String path;

    public FileProcessor(String path) {
        this.path = path;
    }

    public int process() {
        try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
            int result = 0;
            String line = null;
            while ((line = reader.readLine()) != null) {
                result += Integer.parseInt(line);
            }
            return result;
        } catch (IOException e) {
            throw new IllegalArgumentException(path + "에 해당하는 파일이 없습니다.", e);
        }
    }
}

 

→ 이것에 대한 문제점은, 만약 -, x, % 등의 연산이 해당 클래스에 추가된다고 하였을 때 파일을 불러오고 읽어들이는 부분이 중복되고 맙니다.

이러한, 정해진 틀 혹은 흐름을 고정적인 템플릿으로 두고, 변경되는 알고리즘(사칙연산) 의 구현만을 하위 클래스에 맡겨버리는게 템플릿 메소드 패턴입니다.

👇

 

템플릿 메소드 패턴 적용 후

사칙연산의 부분을 추상 메소드로  추출합니다.

 

FileProcessor

  • 흐름은 동일하고, 사칙연산의 부분만 abstract 메소드로 추출하였습니다.
public abstract class FileProcessor {

    private String path;

    FileProcessor(String path) {
        this.path = path;
    }

    public int process() {
        try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
            int result = 0;
            String line = null;
            while ((line = reader.readLine()) != null) {
                result = getResult(result, Integer.parseInt(line));
            }
            return result;
        } catch (IOException e) {
            throw new IllegalArgumentException(path + "에 해당하는 파일이 없습니다.", e);
        }
    }

    protected abstract int getResult(int result, int line);
}

 

하위 클래스

  • 구체적인 구현을 하위클래스로 확장해 나갑니다.
//----- Plus --------
public class Plus extends FileProcessor {

    public Plus(String path) {
        super(path);
    }

    @Override
    protected int getResult(int result, int line) {
        return result += line;
    }
}

//----- Minus --------
public class Minus extends FileProcessor {

    public Minus(String path) {
        super(path);
    }

    @Override
    protected int getResult(int result, int line) {
        return result -= line;
    }
}

 

Client

  • 클라이언트에서는 원하는 기능을 구현한 하위클래스를 호출하여 결과값을 얻습니다.
public class Client {

    public static void main(String[] args) {
        FileProcessor fileProcessor_plus = new Plus("number.txt");
        int plus_result = fileProcessor_plus.process();
        System.out.println(plus_result);


        FileProcessor fileProcessor_minus = new Minus("number.txt");
        int minus_result = fileProcessor_minus.process();
        System.out.println(minus_result);
    }
}

 

✔️ 템플릿 메소드 패턴 장점, 단점

• 장점

  1. 템플릿 코드를 재사용하고 중복코드를 줄일 수 있다.
  2. 템플릿 코드를 변경하지 않고 상속을 받아서 구체적인 알고리즘만 변경할 수 있다.

 

• 단점

  1. 리스크프 치환 원칙을 위반할 수도 있다.
    • 리스코프 치환원칙이란 - 리스코프 치환 원칙의 핵심은 부모 클래스의 행동 규약을 자식 클래스가 위반하면 안 된다는 것입니다.
    • 상속을 받은순간 하위클래스가 템플릿을 정의한 메소드를 잘못 오버라이딩하여 부모의 규약을 위반할 가능성이 있다는 것입니다.
  2. 알고리즘 구조가 복잡할 수록 템플릿을 유지하기 어려워진다.

 

 

 

반응형