TypeScript

TypeScript : 객체 지향 프로그래밍 - 1 (Class, 상속)

고래고래00 2024. 7. 2. 20:03

01. 클래스

  • 클래스는 객체 지향 프로그래밍(OOP)의 핵심 구성 요소 중 하나
  • 클래스는 객체를 만들기 위한 틀(template)

클래스의 구성 요소

  • 클래스에서는 같은 종류의 객체들이 공통으로 가지는 속성(attribute)과 메서드(method)를 정의
    • 속성객체의 성질을 결정
    • 메서드객체의 성질을 변화시키거나 객체에서 제공하는 기능들을 사용하는 창구

객체란?

  • 객체는 클래스를 기반으로 생성되며 클래스의 인스턴스(instance)라고도 함

클래스 및 객체 정의 방법

  • TypeScript에서 클래스를 정의하려면 class 키워드 사용
  • 클래스의 속성과 메서드를 정의하고, new 키워드 사용하여 객체를 생성
더보기
class Person {
  name: string;
  age: number;

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

  sayHello() {
    console.log(`안녕하세요! 제 이름은 ${this.name}이고, 나이는 ${this.age}살입니다.`);
  }
}

const person = new Person('Spartan', 30);
person.sayHello();
  • 생성자(constructor)
    • 생성자는 클래스의 인스턴스를 생성하고 초기화하는데 사용되는 메서드
    • 생성자는 클래스 내에서 constructor라는 이름으로 정의
    • 생성자는 인스턴스를 생성할 때 자동으로 호출
    • 생성자는 클래스 내에 오직 하나만 존재할 수 있음
    • 보통, 생성자로 객체 속성을 초기화 하는것 뿐 아니라 객체가 생성이 될 떄 꼭 되어야 하는 초기화 로직을 집어넣기도 함
      • 예를 들어, DBConnector라는 클래스가 있다면 이 클래스 타입의 객체가 생성이 될 때 생성자에서 DB 연결을 미리 해주면 편할 것

접근 제한자

  • 클래스에서는 속성메서드접근 제한자를 사용해 접근을 제한
  • TypeScript에서는 다음의 접근 제한자들 제공
    • public
      • 클래스 외부에서도 접근이 가능한 접근 제한자
      • 접근 제한자가 선언이 안되어있다면 기본적으로 접근 제한자는 public
      • 클래스의 함수 중 민감하지 않은 객체 정보를 열람할 때나 누구나 해당 클래스의 특정 기능을 사용해야 할 때 사용
    • private
      • 클래스 내부에서만 접근이 가능한 접근 제한자
      • 보통은 클래스의 속성은 대부분 private으로 접근 제한자를 설정
        • 즉, 외부에서 직접적으로 객체의 속성을 변경할 수 없게 제한
      • 클래스의 속성을 보거나 편집하고 싶다면 별도의 getter/setter 메서드를 준비해놓는 것이 관례
    • protected
      • 클래스 내부와 해당 클래스를 상속받은 자식 클래스에서만 접근이 가능한 접근 제한자
더보기
class Person {
  private name: string;
  private age: number;

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

  public sayHello() {
    console.log(`안녕하세요! 제 이름은 ${this.name}이고, 나이는 ${this.age}살입니다.`);
  }
}

02. 상속

상속(inheritance)

  • 상속은 객체 지향 프로그래밍에서 클래스 간의 관계를 정의하는 중요한 개념
  • 상속을 통해 기존 클래스의 속성과 메서드를 물려받아 새로운 클래스를 정의
  • 상속이 있어서 똑같은 코드를 계속 반복적으로 작성할 필요 X
  • 상속을 구현하려면 extends 키워드
더보기
class Animal {
  name: string;

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

  makeSound() {
    console.log('동물 소리~');
  }
}

class Dog extends Animal {
  age: number;

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

  makeSound() {
    console.log('멍멍!'); // 부모의 makeSound 동작과 다름
  }

  eat() { // Dog 클래스만의 새로운 함수 정의
    console.log('강아지가 사료를 먹습니다.');
  }
}

class Cat extends Animal { // Animal과 동일
}

const dog = new Dog('누렁이');
dog.makeSound(); // 출력: 멍멍!

const cat = new Cat('야옹이');
cat.makeSound(); // 출력: 동물 소리~
  • 여기서 Animal을 부모 클래스, Dog를 자식 클래스
  • super 키워드는 자식 클래스가 부모 클래스를 참조하는데 사용하는 키워드
    • 즉, 자식 클래스에서 생성자를 정의할 때 부모 클래스의 생성자를 호출해야 할 때 사용

서브타입과 슈퍼타입

더보기

서브타입

  • 두 개의 타입 A와 B가 있고 B가 A의 서브타입이면 A가 필요한 곳에는 어디든 B를 안전하게 사용할 수 있다.

슈퍼타입

  • 두 개의 타입 A와 B가 있고 B가 A의 슈퍼타입이면 B가 필요한 곳에는 어디든 A를 안전하게 사용할 수 있다.
  • any는 모든 것의 슈퍼타입
  • Animal은 Dog, Cat의 슈퍼타입이고요. Dog, Cat은 Animal의 서브타입

upcasting

let dog: Dog = new Dog('또순이');
let animal: Animal = dog; // upcasting
animal.eat(); // 에러. 슈퍼타입(Animal)으로 변환이 되어 eat 메서드를 호출 불가
  • 서브타입 → 슈퍼타입으로 변환을 하는 것을 upcasting
  • 이 경우에는 타입 변환은 암시적으로 이루어져 별도의 타입 변환 구문이 필요 X
    • TypeScript가 자동으로 해줌
    • 위의 코드에서도 단지 슈퍼타입 변수에 대입
  • upcasting의 필요성 : 서브타입 객체를 슈퍼타입 객체로 다루면 유연하게 활용 가능
    • 예를 들어, Dog, Cat, Lion 그리고 기타 등등 다양한 동물을 인자로 받을 수 있는 함수?
      • Animal 타입의 객체를 받으면 모두 다 받을 수 있음 O
      • union으로 새로운 타입을 만들어서 해당 타입의 객체를 받게 함 X

downcasting

let animal: Animal;
animal = new Dog('또순이');

let realDog: Dog = animal as Dog;
realDog.eat(); // 서브타입(Dog)로 변환이 되었기 때문에 eat 메서드를 호출 가능
  • 슈퍼타입 → 서브타입으로 변환을 하는 것을 downcasting
  • 이 경우에는 as 키워드로 명시적으로 타입 변환
  • downcasting의 필요성 : Dog와 같은 서브타입의 메서드를 사용해야 될 때