flagtags / OOP-study

0 stars 0 forks source link

8장. 템플릿 메소드 패턴 #23

Open kkirico opened 4 months ago

kkirico commented 4 months ago

카페인 음료 예시

‘알고리즘을 추상화 한다’

class CaffeinBeverage {
    prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    }

    abstract brew();
    abstract addCondiments();
    boilWater() {
        // boil water
    }
    pourInCup() {
        // pour in cup
    }
}

class Tea extends CaffeinBeverage {
    brew() {
        console.log('brew tea leaf');
    }

    addCondiments() {
        console.log('add lemon')
    }
}

class Coffe extends CaffeinBeverage {
    brew() {
        console.log('filter grinded coffe');
    }

    addCondiments() {
        console.log('add sugar and milk');
    }
}

구현 방식에 약간의 차이가 있지만, 사실상 알고리즘은 동일한 상황입니다.

prepareRecipe는 템플릿 메소드 입니다

템플릿 내에서 알고리즘의 각 단께는 메소드로 표현됩니다.

서브클래스에서 구현해야하는 메소드는 추상 메소드로 표현합니다.

템플릿 메소드는 알고리즘의 각 단계를 정의하며, 서브클래스에서 일부 단계를 구현할 수 있도록 유도합니다.

템플릿 메소드 패턴의 생김새

abstract class AbstractClass{
    readonly templateMethod() {
        primitiveOperation1();
        primitiveOperation2();

        concreteOperation();

        hook();
    }

    abstract primitiveOperation1();
    abstarct primitiveOperation2();

    concreteOperation() {
        // code
    }

    hook() {}
}

hook 알아보기

예시

class CaffeinBevearge{
    readonly prepareRecipe() {
        boilWater();
        brew();
        pourInCup();

        if(customerWantsCondiments()){
            addCondiments();
        }
    }

    // same code

    customerWantsCondiments() {
        return true;
    }
}

class CoffeeWithHook() {
    // ...

    customerWantsCondiments() {
        if(customers.answer === true){
            return true;
        }
        return false;
    }
}

음료에 첨가물을 추가할지 결정하는 hook을 서브클래스에서 정의한다.

서브클래스가 알고리즘의 특정 단계를 제공해야 할 때는 추상 메소드를 사용합니다.

알고리즘의 단계가 선택적으로 적용된다면 훅을 사용합니다.

추상 메소드가 너무 많아지면 서브클래스에서 구현해야할 것이 너무 많아집니다. 따라서 알고리즘의 단계를 너무 잘게 쪼개지 않는 것도 중요합니다.

할리우드 원칙

고수준 구성요소가 저수준 구성요소에게 “먼저 연락하지 마세요, 제가 연락 드리겠습니다”

저수준 구성요소는 절대 고수준 구성요소를 호출할 수 없습니다.

템플릿 메소드 패턴에서 템플릿 메소드가 서브클래스를 호출합니다. 서브클래스는 고수준 구성요소인 부모클래스를 호출 할 수 없습니다.

전략 패턴과의 비교

전략패턴과의 차이점은 알고리즘을 추상화 한다는 목표?인건가?

전략패턴은 각기다른 알고리즘을 서브클래스에서 정의하고 구성으로 넘겨준다

템플릿 메소드 패턴은 하나의 알고리즘에 다른 부분들을 서브클래스에서 정의하도록 템플릿을 제공한다.

j03y14 commented 4 months ago
class Coffee {
    prepareRecipe(): void {
        this.boilWater();
        this.brewCoffeeGrinds();
        this.pourInCup();
        this.addSugarAndMilk();
    }

    boilWater(): void {
        console.log('물 끓이는 중');
    }

    brewCoffeeGrinds(): void {
        console.log('커피 내리는 중');
    }

    pourInCup(): void {
        console.log('컵에 따르는 중');
    }

    addSugarAndMilk(): void {
        console.log('설탕과 우유를 추가하는 중');
    }
}

class Tea {
    prepareRecipe(): void {
        this.boilWater();
        this.steepTeaBag();
        this.pourInCup();
        this.addLemon();
    }

    boilWater(): void {
        console.log('물 끓이는 중');
    }

    steepTeaBag(): void {
        console.log('차를 우리는 중');
    }

    pourInCup(): void {
        console.log('컵에 따르는 중');
    }

    addLemon(): void {
        console.log('레몬을 추가하는 중');
    }
}

커피와 홍차의 제조 알고리즘이 똑같다.

추상화하면

abstract class CaffeineBeverage {
    abstract prepareRecipe(): void 

    boilWater(): void {
        console.log('물 끓이는 중');
    }

    pourInCup(): void {
        console.log('컵에 따르는 중');
    }
}

class Coffee extends CaffeineBeverage {
    prepareRecipe(): void {
        this.boilWater();
        this.brewCoffeeGrinds();
        this.pourInCup();
        this.addSugarAndMilk();
    }

    brewCoffeeGrinds(): void {
        console.log('커피 내리는 중');
    }

    addSugarAndMilk(): void {
        console.log('설탕과 우유를 추가하는 중');
    }
}

class Tea extends CaffeineBeverage {
    prepareRecipe(): void {
        this.boilWater();
        this.steepTeaBag();
        this.pourInCup();
        this.addLemon();
    }

    steepTeaBag(): void {
        console.log('차를 우리는 중');
    }

    addLemon(): void {
        console.log('레몬을 추가하는 중');
    }
}

여기서 더 추상화를 할 수 없나?

brewCoffeeGrinds랑 steepTeaBag이랑은 거의 다르지 않다. brew로 통일하자. addLemon, addSugarAndMilk도 마찬가지. addContiment로 통일.

abstract class CaffeineBeverage {
    prepareRecipe(): void {
        this.boilWater();
        this.brew();
        this.pourInCup();
        this.addCondiments();
    } 

    abstract brew(): void;

    abstract addCondiments(): void;

    boilWater(): void {
        console.log('물 끓이는 중');
    }

    pourInCup(): void {
        console.log('컵에 따르는 중');
    }
}

class Coffee extends CaffeineBeverage {
    brew(): void {
        console.log('커피 내리는 중');
    }

    addCondiments(): void {
        console.log('설탕과 우유를 추가하는 중');
    }
}

class Tea extends CaffeineBeverage {
    brew(): void {
        console.log('차를 우리는 중');
    }

    addCondiments(): void {
        console.log('레몬을 추가하는 중');
    }
}

템플릿 메서드 패턴

다른 알고리즘의 템플릿 역할을 하는 메서드를 만들어놓고, 서브클래스에서 일부 단계를 구현할 수 있도록 한다.

후크?

추상클래스에서 선언되지만 기본적인 내용만 구현되어 있거나 아무 코드도 들어있지 않은 메서드.

후크를 사용하려면 서브 클래스에서 후크를 오버라이드 해야된다.

abstract class CaffeineBeverageWithHook {
    prepareRecipe(): void {
        this.boilWater();
        this.brew();
        this.pourInCup();
        if (this.customerWantsCondiments()) {
            this.addCondiments();
        }
    } 

    abstract brew(): void;

    abstract addCondiments(): void;

    customerWantsCondiments(): boolean {
        return true;
    };

    boilWater(): void {
        console.log('물 끓이는 중');
    }

    pourInCup(): void {
        console.log('컵에 따르는 중');
    }
}

class Coffee extends CaffeineBeverageWithHook {
    brew(): void {
        console.log('커피 내리는 중');
    }

    addCondiments(): void {
        console.log('설탕과 우유를 추가하는 중');
    }

    customerWantsCondiments(): boolean {
        const answer = 'y'; // getUserInput();

        if (answer === 'y') {
            return true;
        }

        return false;
    }
}

알고리즘의 특정 단계가 선택적으로 적용된다면 후크를 쓰면 된다.

할리우드 원칙

할리우드 원칙 - 먼저 연락하지 마세요. 저희가 연락드리겠습니다.

저수준 요소가 고수준 요소에 의존하게 하지 마라.

전략패턴과 비교