상속의 문제점 ?
=> 여러개의 클래스를 상속받아 사용하는 경우, 그 규모가 커질 수록 클래스간의 관계가 복잡해질 수 있음
순양은 이제 장자상속 없다
Composition
: 각각의 기능별로 클래스를 분리해 만들어 둠으로써 필요할때마다 가져다가 쓰는 것
아래의 예제에서는 우유를 추가하는 클래스, 설탕을 추가하는 클래스를 따로 분리해보자
{
/**
* Composition
*/
type CoffeeCup = {
shots: number;
hasMilk?: boolean; // Optional
hasSugar?: boolean; // Optional
};
interface CoffeeMaker {
makeCoffee(shots: number): CoffeeCup;
}
class CoffeeMachine implements CoffeeMaker {
private static BEANS_GRAMM_PER_SHOT: number = 7; // class level
private coffeeBeans: number = 0; // instance level
// 인스턴스를 만들 때 호출되는 함수
public constructor(coffeeBeans: number) {
this.coffeeBeans = coffeeBeans; // 커피 콩 충전
}
// 클래스 내부의 어떤 속성도 팔요로 하지 않기 떄문에 static으로 함수 선언
static makeMachine(coffeeBeans: number): CoffeeMachine {
return new CoffeeMachine(coffeeBeans);
}
fillCoffeeBeans(beans: number) {
if (beans < 0) {
throw new Error("value for beans should be greater than 0");
}
this.coffeeBeans += beans;
}
private grindBeans(shots: number) {
console.log(`grinding beans for ${shots}`);
if (this.coffeeBeans < shots * CoffeeMachine.BEANS_GRAMM_PER_SHOT) {
throw new Error("Not enough coffee beans!");
}
this.coffeeBeans -= shots * CoffeeMachine.BEANS_GRAMM_PER_SHOT;
}
private preheat(): void {
console.log("heating up,,,");
}
private extract(shots: number): CoffeeCup {
console.log(`Pulling ${shots} shots...`);
return {
shots,
hasMilk: false,
};
}
clean() {
console.log("Cleaning the Machine");
}
makeCoffee(shots: number): CoffeeCup {
this.grindBeans(shots);
this.preheat();
return this.extract(shots);
}
}
// 우유를 넣어주는 클래스
class CheapMilkSteamer {
private steamMilk(): void {
console.log("Steamin some milk...");
}
makeMilk(cup: CoffeeCup): CoffeeCup {
this.steamMilk();
return {
...cup,
hasMilk: true,
};
}
}
// 설탕 제조기
class AutomaticSugarMixer {
private getSugar() {
console.log("Getting some sugar from candy..");
return true;
}
addSugar(cup: CoffeeCup): CoffeeCup {
const sugar = this.getSugar();
return {
...cup,
hasSugar: true,
};
}
}
// CoffeeMachine을 상속하는 클래스
class CaffeLatteMachine extends CoffeeMachine {
constructor(
beans: number,
public readonly serialNumber: string,
private milkFrother: CheapMilkSteamer
) {
super(beans);
}
makeCoffee(shots: number): CoffeeCup {
const coffee = super.makeCoffee(shots);
return this.milkFrother.makeMilk(coffee);
}
}
// CoffeeMachine을 상속하는 클래스
class SweetCoffeeMaker extends CoffeeMachine {
constructor(private beans: number, private sugar: AutomaticSugarMixer) {
super(beans);
}
makeCoffee(shots: number): CoffeeCup {
const coffee = super.makeCoffee(shots);
return this.sugar.addSugar(coffee);
}
}
class SweetCaffeLatteMachine extends CoffeeMachine {
constructor(
private beans: number,
// 필요한 기능을 가져와서 사용
private sugar: AutomaticSugarMixer,
private milkFrother: CheapMilkSteamer
) {
super(beans);
}
makeCoffee(shots: number): CoffeeCup {
const coffee = super.makeCoffee(shots);
const milkAdded = this.milkFrother.makeMilk(coffee);
return this.sugar.addSugar(milkAdded);
}
}
}
위의 경우에 가져다 쓰는 클래스와의 의존성이 높은 문제가 발생한다.
특정 기능을 하는 클래스가 변경되었을 때, 그 클래스를 가져다 쓰는 클래스들 또한 업데이트가 되어야함!
더 유연하고 확장이 가능하게 하려면?
인터페이스로 분리해보자
=> 원하는 Steamer, 원하는 Sugar 클래스를 부품처럼 가져다가 조립하면서 쓸 수 있음...!
{
/**
* 강력한 Interface
*/
type CoffeeCup = {
shots: number;
hasMilk?: boolean; // Optional
hasSugar?: boolean; // Optional
};
interface CoffeeMaker {
makeCoffee(shots: number): CoffeeCup;
}
class CoffeeMachine implements CoffeeMaker {
private static BEANS_GRAMM_PER_SHOT: number = 7; // class level
private coffeeBeans: number = 0; // instance level
// 인스턴스를 만들 때 호출되는 함수
public constructor(
coffeeBeans: number,
private milk: MilkFrother,
private sugar: SugarProvider
) {
this.coffeeBeans = coffeeBeans; // 커피 콩 충전
}
fillCoffeeBeans(beans: number) {
if (beans < 0) {
throw new Error("value for beans should be greater than 0");
}
this.coffeeBeans += beans;
}
private grindBeans(shots: number) {
console.log(`grinding beans for ${shots}`);
if (this.coffeeBeans < shots * CoffeeMachine.BEANS_GRAMM_PER_SHOT) {
throw new Error("Not enough coffee beans!");
}
this.coffeeBeans -= shots * CoffeeMachine.BEANS_GRAMM_PER_SHOT;
}
private preheat(): void {
console.log("heating up,,,");
}
private extract(shots: number): CoffeeCup {
console.log(`Pulling ${shots} shots...`);
return {
shots,
hasMilk: false,
};
}
clean() {
console.log("Cleaning the Machine");
}
makeCoffee(shots: number): CoffeeCup {
this.grindBeans(shots);
this.preheat();
const coffee = this.extract(shots);
const sugarAdded = this.sugar.addSugar(coffee);
return this.milk.makeMilk(sugarAdded);
}
}
interface MilkFrother {
makeMilk(cup: CoffeeCup): CoffeeCup;
}
interface SugarProvider {
addSugar(cup: CoffeeCup): CoffeeCup;
}
// 우유를 넣어주는 클래스
class CheapMilkSteamer implements MilkFrother {
private steamMilk(): void {
console.log("Steaming some milk...");
}
makeMilk(cup: CoffeeCup): CoffeeCup {
this.steamMilk();
return {
...cup,
hasMilk: true,
};
}
}
class FancyMilkSteamer implements MilkFrother {
private steamMilk(): void {
console.log("Fancy Steaming some milk...");
}
makeMilk(cup: CoffeeCup): CoffeeCup {
this.steamMilk();
return {
...cup,
hasMilk: true,
};
}
}
class ColdMilkSteamer implements MilkFrother {
private steamMilk(): void {
console.log("Cold Steaming some milk...");
}
makeMilk(cup: CoffeeCup): CoffeeCup {
this.steamMilk();
return {
...cup,
hasMilk: true,
};
}
}
class NoMilk implements MilkFrother {
makeMilk(cup: CoffeeCup): CoffeeCup {
return cup;
}
}
// 설탕 제조기
class CandySugarMixer implements SugarProvider {
private getSugar() {
console.log("Getting some sugar from candy..");
return true;
}
addSugar(cup: CoffeeCup): CoffeeCup {
const sugar = this.getSugar();
return {
...cup,
hasSugar: true,
};
}
}
class SugarMixer implements SugarProvider {
private getSugar() {
console.log("Getting some sugar from jar!!..");
return true;
}
addSugar(cup: CoffeeCup): CoffeeCup {
const sugar = this.getSugar();
return {
...cup,
hasSugar: true,
};
}
}
class NoSugar implements SugarProvider {
addSugar(cup: CoffeeCup): CoffeeCup {
return cup;
}
}
// Milk
const cheapMilkMaker = new CheapMilkSteamer();
const fancyMilkMaker = new FancyMilkSteamer();
const coldMilkMaker = new ColdMilkSteamer();
const noMilk = new NoMilk();
// Sugar
const candySugar = new CandySugarMixer();
const sugar = new SugarMixer();
const noSugar = new NoSugar();
const sweetCandyMachine = new CoffeeMachine(12, noMilk, candySugar);
const sweetMachine = new CoffeeMachine(12, noMilk, sugar);
const cheapLatteMachine = new CoffeeMachine(12, cheapMilkMaker, noSugar);
const FancyLatteMachine = new CoffeeMachine(12, fancyMilkMaker, noSugar);
const coldFancyLatteMachine = new CoffeeMachine(12, coldMilkMaker, noSugar);
const sweetLatteMachine = new CoffeeMachine(12, cheapMilkMaker, sugar);
}
사실 드립 내 취향임 ㅎ 누군데 당신 ㅎ
'Web > TypeScript' 카테고리의 다른 글
[TypeScript] Utility Type (0) | 2023.03.05 |
---|---|
[TypeScript] 제네릭 (Generics) (0) | 2023.03.01 |
[TypeScript] 객체지향 (OOP) (0) | 2023.02.24 |
[TypeScript] 기본 타입 #2 (0) | 2023.02.22 |
[TypeScript] 기본 타입 #1 (0) | 2023.02.21 |
댓글