flagtags / OOP-study

0 stars 0 forks source link

3장 - 데코레이터 패턴 #13

Open j03y14 opened 5 months ago

j03y14 commented 5 months ago

스타버즈

abstract class Beverage{
    abstract cost(): number
    abstract getDescription(): string
}

// 이런식으로 전부 다 만들어야함
class HouseBlendWithSteamedMilk extends Beverage{
    cost(): number {
        return 1.5
    }
    getDescription(): string {
        return 'HouseBlendWithSteamedMilk'
    }
} 

클래스가 너무 많아지는 문제가 발생.

두가지 디자인 원칙을 지키지 않고 있음.

  1. 상속보다 구성을 사용해라.
  2. 변경되는 것들에 대한 캡슐화가 되지 않았다?

첨가물을 super class에서 관리하면 되지 않나?

abstract class Beverage{
    description: string;
    milk: boolean;
    caramel: boolean;
    whip: boolean;
    soy: boolean;

    // 첨가물의 가격을 계산하는 메서드
    cost(): number {
        let cost = 0
        if(this.milk) cost += 0.5
        if(this.caramel) cost += 0.5
        if(this.whip) cost += 0.5
        if(this.soy) cost += 0.5
        return cost
    }

    abstract getDescription(): string
}

// 이런식으로 전부 다 만들어야함
class HouseBlendWithSteamedMilk extends Beverage{
    cost(): number {
        // 음료 가격에 첨가물 가격을 더한다.
        return 0.7 + super.cost()
    }
    getDescription(): string {
        return 'HouseBlendWithSteamedMilk'
    }
}

이 코드의 문제점

상속의 문제점

상속을 받으면 행동이 컴파일 할 때 완전히 결정된다. 대신 구성을 사용하면 동적으로 행동을 설정할 수 있다. 구성을 사용하면 기존 코드를 고치는 대신 확장할 수 있다.

❗디자인 원칙5 - 클래스는 확장에는 열려있어야 하지만 변경에는 닫혀 있어야 한다.

코드를 확장해야할 부분을 선택할 때는 세심한 주의가 필요하다. 무조건 OCP를 적용하면 괜히 쓸데없는 일을 하며 시간 낭비를 할 수 있다.

데코레이터 패턴

특정 음료에서 시작해서 음료를 장식해보자(데코레이터 패턴)

  1. DarkRoast 객체에서 시작
  2. Mocha 객체를 만들어 DarkRoast 객체를 감싼다.
  3. Whip으로 Mocha를 감산다.

이를 가능하게 하기 위해서,

데코레이터 패턴의 정의

객체에 추가적인 요소를 동적으로 더해서 유연하게 기능을 확장할 수 있도록 하는 패턴

데코레이터 패턴으로 코드 수정

class HouseBlend extends Beverage{
    cost(): number {
        return 1.5
    }
}

class DarkRoast extends Beverage{
    cost(): number {
        return 1.2
    }

    getDescription(): string {
        return 'DarkRoast'
    }
}

class Decaf extends Beverage{
    cost(): number {
        return 1.3
    }

    getDescription(): string {
        return 'Decaf'
    }

}

// 첨가물

abstract class CondimentDecorator extends Beverage{
        beverage: Beverage;
    abstract getDescription(): string
}

class Milk extends CondimentDecorator{
    beverage: Beverage

    constructor(beverage: Beverage){
        super()
        this.beverage = beverage
    }

    cost(): number {
        return 0.5 + this.beverage.cost()
    }

    getDescription(): string {
        return this.beverage.getDescription() + ', Milk'
    }
}

class Soy extends CondimentDecorator{
    beverage: Beverage

    constructor(beverage: Beverage){
        super()
        this.beverage = beverage
    }

    cost(): number {
        return 0.3 + this.beverage.cost()
    }

    getDescription(): string {
        return this.beverage.getDescription() + ', Soy'
    }
}

데코레이터 패턴의 한계점?

kkirico commented 5 months ago

3장 데코레이터 패턴

상속을 남용하는 상태를 살펴보고, 객체 작성 이라는 형식으로 실행 중에 클래스를 꾸미는 방법을 배웁니다.

기존 클래스를 고치지 않고도 객체에 새로운 역할을 추가할 수 있습니다.

음료 제작 클래스

커피 주문에 따라 들어가는 재료의 경우의 수가 모두 개별적으로 생성해야한다.

재료의 가격이 인상되거나, 새로운 재료가 추가된다면?

지켜지지 않은 디자인 원칙은 아마 2, 4 인듯?

  1. 구현보다는 인터페이스에 맞춰서 프로그래밍한다.
  2. 상속보다는 구성을 활용한다.
  3. 상호작용하는 객체 사이에는 가능하면 느슨한 결합을 사용해야한다.
  4. 애플리케이션에서 달라지는 부분을 찾아내고 달라지지 않는 부분과 분리한다

이를 해결하기 위해 각 첨가물에 해당하는 boolean 변수를 추가하고

각 메뉴마다 서브클래스를 만든다.

public class Beverage {
    public double cost() {
        double condiment  = 0.0;
        if(hasMilk()) {
            condiment += milkCost;
        }
        if(hasSoy()) {
            condimentCost += soyCost;
        }
        // ...
    }
}

public class DarkRoast extends Beverage {
    public DarkRoast() {
        description = 'the best dark roast coffee';
    }

    public double cost() {
        return 1.99 + super.cost();
    }
}

첨가물의 가격이 바뀌거나 첨가물의 종류가 많아지면 코드를 수정해야한다.

불필요한 메서드를 항상 상속받는 클래스가 존재한다.

첨가물이 두번 들어간 음료가 있다면?

상속이 강력하긴 하나 구성으로 객체를 확장하면 실행 중에 행동을 상속할 수 있다

디자인 원칙 클래스는 확장에는 열려있어야 하지만 변경에는 닫혀있어야 한다.

상속을 써서 음료 가격과 첨가물 가격을 합해서 총 가격을 산출하는 방법은 그리 좋은 방법이 아니었다. 일단 특정 음료에서 시작해서 첨가물로 그 음료를 장식하는 방법을 생각해보자

데코레이터 패턴으로 객체에 추가요소를 동적으로 더할 수 있습니다. 데코레이터를 사용하면 서브클래스를 만들 때 보다 훨씬 유연하게 기능을 확장할 수 있습니다.

팩토리나 빌더같은 다른 패턴으로 만들고 사용하게 된다.