icon
4장 : 클래스와 인터페이스

상속과 구현


객체 지향 프로그래밍의 핵심 원칙 중 하나는 코드의 재사용성확장성입니다. 타입스크립트의 클래스는 이러한 목표를 달성하기 위해 상속(Inheritance)구현(Implementation) 이라는 강력한 메커니즘을 제공합니다. 이 두 개념은 클래스 간의 관계를 정의하고, 기존 코드 베이스를 재활용하여 새로운 기능을 구축할 수 있도록 돕습니다.

이 절에서는 타입스크립트에서 상속과 구현이 어떻게 작동하는지, 그리고 언제 어떤 것을 사용해야 하는지 자세히 살펴보겠습니다.


상속 (Inheritance)

상속은 한 클래스(자식 클래스 또는 서브클래스)가 다른 클래스(부모 클래스 또는 슈퍼클래스)의 속성과 메서드를 물려받아 재사용하고 확장하는 개념입니다. 이를 통해 코드 중복을 줄이고, 논리적인 계층 구조를 형성할 수 있습니다. 타입스크립트에서는 extends 키워드를 사용하여 상속을 구현합니다.

// 부모 클래스(Superclass) 정의
class Animal {
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  // 부모 클래스의 메서드
  move(distanceInMeters: number = 0): void {
    console.log(`${this.name}${distanceInMeters}m 이동했습니다.`);
  }
}

// 자식 클래스(Subclass) 정의: Animal 클래스를 상속받습니다.
class Dog extends Animal {
  breed: string; // Dog 클래스만의 새로운 속성

  constructor(name: string, breed: string) {
    // 자식 클래스의 생성자에서는 반드시 super()를 호출하여
    // 부모 클래스의 생성자를 먼저 실행해야 합니다.
    super(name);
    this.breed = breed;
  }

  // 부모 클래스의 메서드를 오버라이딩(Overriding)할 수 있습니다.
  move(distanceInMeters: number = 5): void {
    console.log("개가 꼬리를 흔듭니다.");
    // super 키워드를 사용하여 부모 클래스의 메서드를 호출할 수 있습니다.
    super.move(distanceInMeters);
  }

  // Dog 클래스만의 새로운 메서드
  bark(): void {
    console.log(`${this.name} (${this.breed})가 멍멍 짖습니다!`);
  }
}

class Cat extends Animal {
  constructor(name: string) {
    super(name);
  }

  // 새로운 메서드
  purr(): void {
    console.log(`${this.name}가 야옹하고 골골거립니다.`);
  }
}

const myDog = new Dog("바둑이", "시바견");
myDog.bark();       // 바둑이 (시바견)가 멍멍 짖습니다!
myDog.move(10);     // 개가 꼬리를 흔듭니다., 바둑이가 10m 이동했습니다. (오버라이딩된 메서드 호출)

const myCat = new Cat("나비");
myCat.purr();       // 나비가 야옹하고 골골거립니다.
myCat.move();       // 나비가 0m 이동했습니다. (부모의 메서드 호출)

// 타입 검사: Dog와 Cat은 Animal 타입으로 간주될 수 있습니다. (다형성)
let animalFarm: Animal[] = [myDog, myCat];
animalFarm.forEach(animal => {
  animal.move(5); // 모든 Animal 타입은 move 메서드를 가집니다.
});

상속의 주요 특징

  • extends 키워드: 자식 클래스가 부모 클래스의 모든 publicprotected 멤버(속성과 메서드)를 상속받습니다. private 멤버는 상속되지 않습니다.
  • super() 호출: 자식 클래스의 생성자에서는 this를 사용하기 전에 반드시 super()를 호출하여 부모 클래스의 생성자를 실행해야 합니다. 이는 부모 클래스에 정의된 속성들을 초기화하기 위함입니다.
  • 메서드 오버라이딩 (Overriding): 자식 클래스에서 부모 클래스와 동일한 이름의 메서드를 재정의할 수 있습니다. super 키워드를 사용하여 오버라이딩된 메서드 내에서 부모의 원본 메서드를 호출할 수도 있습니다.
  • 다형성 (Polymorphism): 상속 계층 구조에 있는 클래스들은 서로 다른 타입을 가질 수 있지만, 공통의 부모 타입으로 취급될 수 있습니다. (예: Dog 인스턴스가 Animal 타입의 배열에 들어갈 수 있음)

구현 (Implementation)

구현은 클래스가 특정 인터페이스(Interface) 에 정의된 모든 속성과 메서드를 반드시 갖추고 있음을 약속하는 개념입니다. 즉, 클래스가 인터페이스의 계약(Contract) 을 이행한다고 선언하는 것입니다. 타입스크립트에서는 implements 키워드를 사용하여 구현을 정의합니다.

// 'Runnable' 인터페이스 정의: 'run' 메서드를 가져야 한다고 약속합니다.
interface Runnable {
  run(): void;
  speed: number;
}

// 'Walkable' 인터페이스 정의: 'walk' 메서드를 가져야 한다고 약속합니다.
interface Walkable {
  walk(distance: number): void;
}

// 'Robot' 클래스가 Runnable과 Walkable 인터페이스를 구현합니다.
class Robot implements Runnable, Walkable {
  speed: number; // Runnable 인터페이스의 speed 속성 구현

  constructor(initialSpeed: number) {
    this.speed = initialSpeed;
  }

  run(): void { // Runnable 인터페이스의 run 메서드 구현
    console.log(`로봇이 ${this.speed}km/h 속도로 달립니다.`);
  }

  walk(distance: number): void { // Walkable 인터페이스의 walk 메서드 구현
    console.log(`로봇이 ${distance}m 걸었습니다.`);
  }

  // Robot 클래스만의 추가 메서드
  scan(): void {
    console.log("주변을 스캔합니다.");
  }
}

// 'Vehicle' 클래스는 Runnable 인터페이스만 구현합니다.
class Vehicle implements Runnable {
  speed: number;

  constructor(initialSpeed: number) {
    this.speed = initialSpeed;
  }

  run(): void {
    console.log(`차량이 ${this.speed}km/h 속도로 주행합니다.`);
  }
}

const robot = new Robot(10);
robot.run();   // 로봇이 10km/h 속도로 달립니다.
robot.walk(5); // 로봇이 5m 걸었습니다.
robot.scan();  // 주변을 스캔합니다.

const car = new Vehicle(100);
car.run();     // 차량이 100km/h 속도로 주행합니다.

// 타입 검사: 인터페이스 타입으로 변수를 선언할 수 있습니다.
let myRunner: Runnable = robot; // Robot 인스턴스는 Runnable 인터페이스를 만족합니다.
myRunner.run(); // 로봇이 10km/h 속도로 달립니다.
// myRunner.walk(1); // Error: 'Runnable' 형식에 'walk' 속성이 없습니다. (Runnable에는 walk가 정의되어 있지 않음)

myRunner = car; // Vehicle 인스턴스도 Runnable 인터페이스를 만족합니다.
myRunner.run(); // 차량이 100km/h 속도로 주행합니다.

구현의 주요 특징

  • implements 키워드: 클래스가 하나 또는 여러 개의 인터페이스에 정의된 모든 멤버를 반드시 구현해야 한다고 명시합니다. 만약 인터페이스의 멤버를 모두 구현하지 않으면 컴파일 오류가 발생합니다.
  • 계약(Contract): 인터페이스는 "이러한 기능을 제공해야 한다"는 약속(계약)을 정의하고, 클래스는 그 약속을 "이행"합니다.
  • 다중 구현: 자바스크립트는 다중 상속을 지원하지 않지만, 타입스크립트의 클래스는 여러 인터페이스를 동시에 implements 할 수 있습니다. 이는 여러 기능 집합을 한 클래스에 부여할 때 유용합니다.
  • 타입 확인: 인터페이스는 클래스가 특정 형태를 가지고 있음을 보증하므로, 인터페이스 타입으로 변수를 선언하고 해당 클래스의 인스턴스를 할당할 수 있습니다. (예: let myRunner: Runnable = robot;)

상속과 구현의 차이점 및 사용 시기

특징상속 (extends)구현 (implements)
관계"Is-a" 관계 (A는 B의 일종이다)"Can-do" 관계 (A는 B의 기능을 할 수 있다)
코드 재사용부모 클래스의 구현(코드)을 물려받아 재사용인터페이스에 정의된 멤버를 클래스에서 "반드시" 구현해야 함
클래스 개수단일 상속만 가능 (하나의 부모 클래스만 가질 수 있음)다중 구현 가능 (여러 인터페이스를 구현할 수 있음)
목적공통된 속성/행동을 가진 클래스 간의 계층 구조 형성, 코드 재사용 및 확장특정 동작을 수행해야 함을 계약으로 명시, 다형성 증진, 코드 구조화
누가 누구인가?자식 클래스는 부모 클래스의 한 종류이다.클래스는 인터페이스의 계약을 따른다.

언제 무엇을 사용해야 할까?

  • 상속 (extends)

    • 두 클래스 사이에 명확한 "Is-a" 관계가 성립할 때 (예: DogAnimal이다).
    • 공통된 기본 구현을 공유하고, 자식 클래스에서 해당 구현을 확장하거나 오버라이딩해야 할 때.
    • 코드 중복을 줄이고 싶을 때.
  • 구현 (implements)

    • 특정 기능을 제공해야 한다는 "계약"을 강제하고 싶을 때.
    • 서로 다른 상속 계층에 있는 클래스들이 동일한 기능을 수행해야 할 때 (예: RobotVehicle은 둘 다 Runnable일 수 있음).
    • 클래스의 역할을 명확히 하고, 다형성을 통해 유연한 코드를 작성하고 싶을 때.
    • 자바스크립트의 단일 상속 제약으로 인해 다중 상속이 불가능하지만, 여러 인터페이스의 기능을 한 클래스에 부여하고 싶을 때 (다중 구현).

대부분의 경우, 이 두 가지 개념은 상호 보완적으로 사용됩니다. 클래스는 특정 부모 클래스를 상속받으면서 동시에 여러 인터페이스를 구현하여 다양한 역할을 수행할 수 있습니다.


상속과 구현은 객체 지향 설계를 이해하고 타입스크립트를 효과적으로 활용하는 데 필수적인 개념입니다. extendsimplements 키워드의 의미와 역할을 명확히 구분하고, 여러분의 애플리케이션 구조에 맞는 적절한 방식을 선택하여 견고하고 확장 가능한 코드를 작성하시길 바랍니다.