type CoffeeCup = {
shots: number;
hasMilk?: boolean;
hasSugar?: boolean;
};
*// interface*
interface CoffeeMakerA {
makeCoffee(shots: number): CoffeeCup;
}
*// Parent Class : CoffeeMaker*
class CoffeeMaker implements CoffeeMakerA {
private static BEANS_GRANS_PER_SHOT = 7;
constructor(
*// Composition 의 방법으로 상속말 Class 멤버변수에 해당 객체를 가져다 쓰는 방법*
private coffeeBeans: number,
private milk: MilkFrother, *// 우유거품기 인터페이스*
private sugar: SugarProvider *// 설탕제조기 인터페이스*
) {
this.coffeeBeans = coffeeBeans;
}
fillCoffeeBeans(beans: number) {
if (beans < 0) {
throw new Error('beans not enoughf');
}
this.coffeeBeans += beans;
}
private grindBeans(shots: number) {
console.log(`grinding beans for ${shots}`);
if (this.coffeeBeans < shots * CoffeeMaker.BEANS_GRANS_PER_SHOT) {
throw new Error('Not enough coffee beans!');
}
this.coffeeBeans -= shots * CoffeeMaker.BEANS_GRANS_PER_SHOT;
}
private preheat(): void {
console.log('heating up...🔥');
}
private extract(shots: number): CoffeeCup {
console.log(`Pulling ${shots} shots...☕`);
return {
shots: shots,
hasMilk: false,
}
}
public makeCoffee(shots: number): CoffeeCup { *// public 은 생략 되어 있음*
this.grindBeans(shots);
this.preheat();
const coffee = this.extract(shots);
const addsugar = this.sugar.addSugar(coffee);
return this.milk.makeMilk(addsugar);
}
}
*// interface (우유 거품기)*
interface MilkFrother {
makeMilk(cup: CoffeeCup): CoffeeCup;
}
*// interface (설탕 제조기)*
interface SugarProvider {
addSugar(cup: CoffeeCup): CoffeeCup;
}
*// 우유 거품기(1)*
class CheapMilkSteamer implements MilkFrother {
private steamMilk(): void {
console.log('Steaming some milk...');
}
makeMilk(cup: CoffeeCup): CoffeeCup {
this.steamMilk();
return {
...cup,
hasMilk: true,
}
}
}
*// 우유 거품기(2)*
class FancyMilkSteamer implements MilkFrother {
private steamMilk(): void {
console.log('Steaming some milk...');
}
makeMilk(cup: CoffeeCup): CoffeeCup {
this.steamMilk();
return {
...cup,
hasMilk: true,
}
}
}
*// 설탕 제조기(1)*
class CandySugarMixer implements SugarProvider {
private getSuger() {
console.log('Gettin some sugar from candy');
return true;
}
addSugar(cup: CoffeeCup): CoffeeCup {
const sugar = this.getSuger();
return {
...cup,
hasSugar: sugar,
}
}
}
*// 설탕 제조기(2)*
class SugarMixer implements SugarProvider {
private getSuger() {
console.log('Gettin some sugar from candy');
return true;
}
addSugar(cup: CoffeeCup): CoffeeCup {
const sugar = this.getSuger();
return {
...cup,
hasSugar: sugar,
}
}
}
*// 호출 유형 & 방법 예시 기억해 두기 ★★★*
*// Milk*
const cheapMilkMaker = new CheapMilkSteamer();
const fancyMilkMaker = new FancyMilkSteamer();
*// Sugar*
const candySugar = new CandySugarMixer();
const sugar = new SugarMixer();
*// Machine*
const sweetLatteMachine = new CoffeeMaker(12, cheapMilkMaker, candySugar);
const coldLatteMachine = new CoffeeMaker(12, fancyMilkMaker, sugar );
const seetLatteMachine2 = new CoffeeMaker(12, cheapMilkMaker, candySugar);
[ 구성, 구성요소 - Composition ]
Favor Composition over inheritance : 상속보다 Composition을 더 선호하라!
라는 말이 있다. 우리가 레고를 만들 때 필요한 부품들을 가져와서 조립해 나가는 것처럼 Composition 도 필요한 것을 가져와서 조립해 나가는 것을 의미한다.
그렇다고 상속이 나쁜 건 아니지만 너무 상속만 하면 관계가 복잡해 질 수 있기 때문에 Composition 과 적절히 사용하는 것이 중요하다.
class 간의 단점 해결 방법
클래스들 사이에서 상호작용하는 경우에, 클래스들간의 의사소통이 발생하는 경우에는
클래스 자신을 노출하는 것이 아니라 계약서에 의해서 의사소통을 해야한다.
여기서 계약서는 인터페이스 이다.
즉, 인터페이스를 통해서 서로 간의, 상호 간의 의사소통이 이루어져야 한다.
이것이 디커플링의 원칙이다.
→ 위의 예제 처럼 우유거품기(1, 2) / 설탕제조기(1, 2) 클래스가 있고 이후 여러 클래스가 추가가 된다면 우유거품기 인터페이스 / 설탕제조기 인터페이스 를 통해 다른 클래스들 간의 의사소통을 수월하게 하여 디커플링 원칙을 따른다.
마치면서, overEnginnering 하지 마라!