본문 바로가기
Delvelopment/DesignPattern

[Design Pattern] 템플릿 메서드 패턴 (Template Method Pattern)

by 제제킴 2022. 7. 3.
반응형

**템플릿 메소드 패턴(Template Method Pattern)**은 알고리즘의 골격을 정의합니다. 템플릿 메소드를 사용하면 알고리즘의 일부 단계를 서브클래스에서 구현할 수 있으며, 알고리즘의 구조는 그대로 유지하면서 알고리즘의 특정 단계를 서브클래스에서 재정의할 수도 있습니다.

간단하게 말하면 템플릿 메소드 패턴은 알고리즘의 템플릿을 만든다. 템플릿이란 그냥 메소드이다. 일련의 단계로 알고리즘을 정의한 메소드이다. 여러 단계 가운데 하나 이상의 단계가 추상 메소드로 정의되며, 그 추상 메소드는 서브클래스에서 구현된다. 이러면 서브클래스가 일부분의 구현을 처리하게하면서도 알고리즘의 구조는 바꾸지 않아도 된다.

package pattern.template.starbucks;

public abstract class CaffeineBeverage {

    public void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    }

    public abstract void brew();

    public abstract void addCondiments();

    public void boilWater() {
        System.out.println("물 끓이는 중");
    }

    public void pourInCup() {
        System.out.println("컵에 따르는 중");
    }
}
package pattern.template.starbucks;

public class Coffee extends CaffeineBeverage {

    @Override
    public void brew() {
        System.out.println("필터로 커피를 우려내는 중");
    }

    @Override
    public void addCondiments() {
        System.out.println("설탕과 우유를 추가하는 중");
    }
}
package pattern.template.starbucks;

public class Tea extends CaffeineBeverage {

    @Override
    public void brew() {
        System.out.println("찻잎을 우려내는 중");
    }

    @Override
    public void addCondiments() {
        System.out.println("레몬을 추가하는 중");
    }
}

후크(hook)

후크(hook)는 추상 클래스에서 선언되지만 기본적인 내용만 구현되어 있거나 아무 코드도 들어있지 않은 메소드이다. 이러면 서브클래스는 다양한 위치에서 알고리즘에 끼어들 수 있다.

서브클래스가 추상 클래스에서 진행되는 작업을 처리할지 말지 결정하게 하는 기능을 부여하는 용도로 후크를 사용할 수 있다.

package pattern.template.hook;

public abstract class CaffeineBeverageWithHook {

    void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        if(customerWantsCondiments()){
            addCondiments();
        }
    }

    public boolean customerWantsCondiments() {
        return true;
    }

    abstract void brew();

    abstract void addCondiments();

    void boilWater() {
        System.out.println("물 끓이는 중");
    }

    void pourInCup() {
        System.out.println("컵에 따르는 중");
    }
}
package pattern.template.hook;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class CoffeeWithHook extends CaffeineBeverageWithHook {

    @Override
    public void brew() {
        System.out.println("필터로 커피를 우려내는 중");
    }

    @Override
    public void addCondiments() {
        System.out.println("설탕과 우유를 추가하는 중");
    }

    @Override
    public boolean customerWantsCondiments() {
        String answer = getUserInput();

        if (answer.toLowerCase().startsWith("y")) {
            return true;
        }
        return false;
    }

    private String getUserInput() {
        String answer = null;
        System.out.println("커피에 우유와 설탕을 넣을까요 (y/n)? ");

        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        try {
            answer = in.readLine();
        } catch (IOException e) {
            System.err.println("IO 오류");
        }

        if (answer == null) {
            return "no";
        }
        return answer;
    }
}
package pattern.template.hook;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class TeaWithHook extends CaffeineBeverageWithHook {

    @Override
    public void brew() {
        System.out.println("찻잎을 우려내는 중");
    }

    @Override
    public void addCondiments() {
        System.out.println("레몬을 추가하는 중");
    }

    @Override
    public boolean customerWantsCondiments() {
        String answer = getUserInput();

        if (answer.toLowerCase().startsWith("y")) {
            return true;
        }
        return false;
    }

    private String getUserInput() {
        String answer = null;
        System.out.println("티에 레몬을 넣을까요 (y/n)? ");

        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        try {
            answer = in.readLine();
        } catch (IOException e) {
            System.err.println("IO 오류");
        }

        if (answer == null) {
            return "no";
        }
        return answer;
    }
}
package pattern.template.hook;

public class BeverageTestDrive {
    public BeverageTestDrive() {

        TeaWithHook teaWithHook = new TeaWithHook();
        CoffeeWithHook coffeeWithHook = new CoffeeWithHook();

        System.out.println("\\n홍차 준비 중..");
        teaWithHook.prepareRecipe();

        System.out.println("\\n커피 준비 중..");
        coffeeWithHook.prepareRecipe();

        System.out.println("\\n주문하신 음료가 나왔습니다.\\n");

    }
}
홍차 준비 중..
물 끓이는 중
찻잎을 우려내는 중
컵에 따르는 중
티에 레몬을 넣을까요 (y/n)? 
n

커피 준비 중..
물 끓이는 중
필터로 커피를 우려내는 중
컵에 따르는 중
커피에 우유와 설탕을 넣을까요 (y/n)? 
y
설탕과 우유를 추가하는 중

주문하신 음료가 나왔습니다.

할리우드 원칙 (Hoolywood Principle)

할리우드 원칙을 활요하면 의존성 부패(dependecy rot)를 방지할 수 있다.

어떤 고수준 구성 요소가 저수준 구성 요소에 의존하고, 그 저수준 구성 요소는 다시 고수준 구성 요소에 의존하고 … 가 반복되는 의존성이 복잡하게 꼬여 있는 상황을 의존성이 부패했다고 부른다.

할리우드 원칙을 사용하면 저수준 구성 요소가 시스템에 접속할 수는 있지만 언제, 어떻게 그 구성요소를 사용할지는 고수준 구성요소가 결정한다. 고수준 구성 요소가 저수준 구성요소에게 “먼저 연락하지 마세요. 제가 먼저 연락드리겠습니다.” 라고 말하는 것과 같은 의미이다. 결국 고수준 구성 요소가 어떻게 사용할지는 결정한다.

JAVA API 속 템플릿 메소드 패턴(Tempalate Method Pattern)

템플릿 메소드 패턴을 정말 많이 쓰이는 패턴이다. 이 패턴이 자주 쓰이는 이유는 프레임워크를 만드는 데 아주 훌륭한 디자인 도구이기 때문이다. 프레임워크로 작업이 처리되는 방식을 제어하면서도 프레임워크에서 처리하는 알고리즘의 각 단계를 사용자가 마음대로 지정할 수 있기 때문이다.

Arrays.sort

InputStream.read

  • Arrays.sort를 사용해 오리 정렬하기.
package pattern.template.javaapi;

public class Duck implements Comparable<Duck> {

    String name;
    int weight;

    public Duck(String name, int weight) {
        this.name = name;
        this.weight = weight;
    }

    @Override
    public String toString() {
        return name + " 체중: " + weight;
    }

    @Override
    public int compareTo(Duck otherDuck) {
        if (this.weight < otherDuck.weight) {
            return -1;
        } else if (this.weight == otherDuck.weight) {
            return 0;
        }
        return 1;
    }
}
package pattern.template.javaapi;

import java.util.Arrays;

public class DuckSortTestDrive {
    public DuckSortTestDrive() {
        Duck[] ducks = {
                new Duck("Daffy", 8),
                new Duck("Dewey", 2),
                new Duck("Howard", 7),
                new Duck("Louie", 2),
                new Duck("Donald", 10),
                new Duck("huey", 2)
        };

        System.out.println("정렬 전:");
        display(ducks);

        Arrays.sort(ducks);

        System.out.println("\\n정렬 후:");
        display(ducks);
    }

    private void display(Duck[] ducks) {
        for (Duck d : ducks){
            System.out.println(d);
        }
    }
}
정렬 전:
Daffy 체중: 8
Dewey 체중: 2
Howard 체중: 7
Louie 체중: 2
Donald 체중: 10
huey 체중: 2

정렬 후:
Dewey 체중: 2
Louie 체중: 2
huey 체중: 2
Howard 체중: 7
Daffy 체중: 8
Donald 체중: 10

AbstractList<String>

public class MyStringList extends AbstractList<String> {

    private String[] myList;

    public MyStringList(String[] myList) {
        this.myList = myList;
    }

    @Override
    public String get(int index) {
        return myList[index];
    }

    @Override
    public int size() {
        return myList.length;
    }

    public String set(int index, String item) {
        String oldString = myList[index];
        myList[index] = item;
        return oldString;
    }
}
반응형

댓글