Book/개발

[ 헤드퍼스트 디자인패턴 ] implements 와 extends

ウリ김영은 2025. 1. 3. 15:08

https://www.yes24.com/Product/Goods/108192370

최근에 읽고 있는 책인데, 

책은 Java를 베이스로 디자인패턴과 세부사항을 설명하지만, 나는 이를 타입스크립트로 작성하는 연습(?)이랄까, 

타입스크립트 공부를 하고 있다. ( + 디자인패턴도 ㅎㅎ)

 

자 그래서 어떤 상황이냐,

 

Beverage라는 추상 클래스가 존재합니다. 판매되는 모든 음료는 이 클래스의 서브클래스가 됩니다.

해당 클래스(Beverage)는 

- description 이라는 인스턴스 변수를 가지고 있는데, 이는 각 서브클래스에서 설정됩니다. 

- getDescription 이라는 메서드를 가지고 있는데, description 변수에 저장된 내용을 가져오는 getter 입니다. 

 

 

Claude에게 코드로 작성해달라고 했다. 

1. Java

// 추상 클래스 Beverage
public abstract class Beverage {
	// protected로 선언되어 서브클래스에서 접근 가능
    protected String description = "Unknown Beverage"; 
    
    public String getDescription() {
        return description;
    }
    
    // 추상 메서드로, 서브클래스에서 반드시 구현되어야 함
    public abstract double cost();
}

// HouseBlend 서브클래스
public class HouseBlend extends Beverage {
    public HouseBlend() {
        description = "House Blend Coffee";
    }
    
    @Override
    public double cost() {
        return 0.89;
    }
}

// DarkRoast 서브클래스
public class DarkRoast extends Beverage {
    public DarkRoast() {
        description = "Dark Roast Coffee";
    }
    
    @Override
    public double cost() {
        return 0.99;
    }
}

// Decaf 서브클래스
public class Decaf extends Beverage {
    public Decaf() {
        description = "Decaf Coffee";
    }
    
    @Override
    public double cost() {
        return 1.05;
    }
}

// Espresso 서브클래스
public class Espresso extends Beverage {
    public Espresso() {
        description = "Espresso";
    }
    
    @Override
    public double cost() {
        return 1.99;
    }
}

 

 

 

2. TypeScript

// 추상 클래스 Beverage
abstract class Beverage {
    protected description: string = "Unknown Beverage";
    
    getDescription(): string {
        return this.description;
    }
    
    abstract cost(): number;
}

// HouseBlend 서브클래스
class HouseBlend extends Beverage {
    constructor() {
        super();
        this.description = "House Blend Coffee";
    }
    
    cost(): number {
        return 0.89;
    }
}

// DarkRoast 서브클래스
class DarkRoast extends Beverage {
    constructor() {
        super();
        this.description = "Dark Roast Coffee";
    }
    
    cost(): number {
        return 0.99;
    }
}

// Decaf 서브클래스
class Decaf extends Beverage {
    constructor() {
        super();
        this.description = "Decaf Coffee";
    }
    
    cost(): number {
        return 1.05;
    }
}

// Espresso 서브클래스
class Espresso extends Beverage {
    constructor() {
        super();
        this.description = "Espresso";
    }
    
    cost(): number {
        return 1.99;
    }
}

// 사용 예시
const espresso = new Espresso();
console.log(espresso.getDescription()); // "Espresso"
console.log(espresso.cost()); // 1.99
  • Java의 public, protected 같은 접근 제어자는 TypeScript에서도 동일하게 사용 가능
  • 생성자에서 super()를 명시적으로 호출
  • TypeScript에서는 메서드 선언 시 function 키워드를 사용하지 않음

타입스크립트에서는 메서드 선언 시 function 키워드를 사용하지 않는 다는 걸 이번에 처음 알게 되었다. 어쩐지 모든 메서드를 bind 해줘야 하는건가 의문이었는데, 이래서 this와 관련된 문제가 계속 생겼던 것 같다. 

 

 

왜 위의 코드에서 extends를 사용했고, implements를 사용하지 않았는지에 대해 알아보고자 한다. 

 

 

일단, 

- extends 는 클래스를 상속할 때 사용한다. ( 클래스 -> 클래스 )

- implements 는 인터페이스를 구현할 때 사용한다. ( 인터페이스 -> 클래스 )

 

 

implements를 사용하려면 Beverage가 interface이어야 하는데,

만약 Beverage가 interface였다면, 

interface Beverage {
    getDescription(): string;
    cost(): number;
}
  • description 변수를 공유할 수 없다.
  • getDescription()의 구현을 공유할 수 없다.
  • 각 서브클래스에서 모든 것을 다시 구현해야 한다.

추상 클래스(Beverage)는 일반 메서드(getDescription())와 추상 메서드(cost())를 모두 가질 수 있고, 인스턴스 변수(description)를 가질 수 있다. 

 

그리고,

인터페이스는 "할 수 있는 것"을 정의할 때 사용하고, 추상 클래스는 "공통된 특성"을 공유할 때 사용한다.

 

 

혹시 extends 와 implements 에 대해서 더 자세히 알고 싶다면, 아래 게시물도 읽어 보면 좋을 것 같다. 

[ TypeScript ] extends vs implements