[ 헤드퍼스트 디자인패턴 ] implements 와 extends
최근에 읽고 있는 책인데,
책은 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