클래스

타입스크립트

타입스크립트에서 역시 자바스크립트처럼 클래스를 지원한다. 하지만 타입 스크립트에서는 좀 더 엄격한 검증을 위해 아래의 몇가지 추가적인 문법이 존재한다.

  1. 접근 제어자(Access Modifiers)
  2. 생성자 매개변수 속성(Parameter Properties)
  3. 읽기전용 속성 (Readonly Properties)
  4. 추상클래스 및 추상 메소드 (Abstract Class & Method)
  5. 인터페이스와 클래스 구현

접근 제어자

Public

어디에서나 접근할 수 있으며, 생략이 가능한 default 이다

class Hello {
  name: string;
 
  constructor(name: string) {
    this.name = name;
  }
 
  public greet() {
    console.log(`hello! ${this.name}`);
  }
}
 
const hello = new Hello('kjh');
hello.greet();

Protected

해당 클래스 혹은 서브클래스의 인스턴스에서만 접근 가능하다

class Hello {
  name: string;
 
  constructor(name: string) {
    this.name = name;
  }
 
  protected greet(): string {
    return `hello! ${this.name}`;
  }
}
 
// public, protected요소 상속받는다
class Hi extends Hello {
  greet(): string {
    return `hi! ${this.name}`;
  }
}
 
const hi = new Hi('kjh');
console.log(hi.greet());

단, 서브클래스에서 protected로 된 값을 public으로 오버라이딩하면 해당 값은 해당 서브 클래스에서 public으로 취급된다

Private

private은 해당 클래스의 인스턴스에서 접근가능하다 즉, private 요소들은 상속되지 않는다

class Hello {
  private name: string;
 
  constructor(name: string) {
    this.name = name;
  }
 
  protected greet(): string {
    return `hello! ${this.name}`;
  }
}
 
// public, protected요소 상속받는다
class Hi extends Hello {
  greet(): string {
    // name을 상속하지 않으므로 에러발생
    return `hi! ${this.name}`;
  }
}
 
const hi = new Hi('kjh');
console.log(hi.greet());

private 요소는 상속되지 않는다

생성자 매개변수 속성(Parameter Properties)

타입스크립트에서 생성자의 매개변수에 접근 제어자를 붙이면, 자동으로 클래스의 속성으로 선언되고 초기화된다

(이전 글 참조)

class Example {
  constructor(private name: string, public age: number) {}
}
const a = new Example('kjh', 24);
console.log(a.name); // 에러
console.log(a.age);  // 24

읽기 전용 속성 (Readonly Properties)

readonly 키워드를 사용하여, 클래스의 속성을 읽기 전용으로 만들 수 있다. 이러한 속성은 초기화된 이후에는 값을 변경할 수 없습니다

class Example {
  readonly id: number;
  constructor(id: number) {
    this.id = id; // 생성자에서 초기화
  }
 
  updateId(newId: number) {
    this.id = newId; // 에러
  }
}
 
const example = new Example(123);
console.log(example.id); // 123
example.updateId(321); // 에러 발생

추상클래스 및 추상 메소드 (Abstract Class & Method)

타입스크립트는 추상클래스를 정의할 수 있다

추상 클래스는 인스턴스화 될 수 없다. 반드시 상속받은 서브 클래스들에서 추상메소드들이 구현되어야 한다

c++ 의 virtual과 비슷한 개념이다 서브클래스에 구현을 위임, 인스턴스화 금지

abstract class Animal {
    abstract makeSound(): void; // 서브클래스에서 반드시 구현해야 함
 
    move(): void {
        console.log("Moving...");
    }
}
 
class Dog extends Animal {
    makeSound() {
        console.log("Bark");
    }
}
 
const dog = new Dog();
dog.makeSound(); // "Bark"
 

abstract 키워드를 사용하여, 추상클래스, 추상 메소드를 정의할 수 있다

그리고 난 문득 이런 생각이 들었다 추상 클래스, 추상 메소드를 왜 사용하지?

일반 클래스도 함수 시그니처만 잘 맞춰주면 상속했을 때 오버라이딩할 수 있고, 우리가 앞으로 배울 인터페이스를 활용하여 형식을 강제시킬 수 있는데??

이에 대한 대답은 강제성과, 부분구현에 있다

  1. 일반클래스도 상속하면 오버라이딩 되는데, 추상클래스가 뭔 소용이야?!? 대답: 일반 클래스는 상속받은 서브클래스들의 함수구현을 강제하진 않는다, 왜냐면 이미 구현이 되어있는걸 상속받는거니까… 하지만 추상클래스를 상속받으면 추상메소드는 미구현상태이므로, 함수의 구현이 강제되어 좀 더 설계상의 의도를 명확히 표현할 수 있다

  2. 인터페이스를 활용할 수도 있는거 아니야?!? 대답: 인터페이스를 활용하는것도 인터페이스의 형식을 강제하는 좋은 방법이긴 하지만, 이는 형식에 불과하다. 즉 인터페이스를 적용한다한들 실제 구현이 상속이 되진 않는다 하지만 추상클래스를 상속받으면 추상메소드를 제외한 나머지 요소들은 전부 구현이 완료된 상태로 상속을 받는것이고, 부분구현만 서브클래스에서 하면된다는 장점이 있다 코드의 재사용성이 증가한다

인터페이스와 클래스 구현

타입스크립트에서는 클래스가 특정 인터페이스를 구현하도록 할 수 있다.인터페이스는 클래스가 따라야할 계약을 정의하며, 인터페이스에 정의된 모든 메소드와, 속성들을 구현해야한다

interface Person {
    name: string;
    speak(): void;
}
 
class Teacher implements Person {
    constructor(public name: string) {}
 
    speak() {
        console.log(`Hello, my name is ${this.name}.`);
    }
}
 

그리고 클래스에 인터페이스를 좀 적용시키면서 느끼는건데 클래스는 좀 애가 까다롭다.. 보통 인터페이스나 타입을 정해주고 나면 따로 타입을 정해주지 않아도 알아서 인터페이스나, 타입에 정해놓은 타입들을 따라 가는데 클래스는 직접 전부 타입들을 적어줘야한다 클래스에게 인터페이스따라 적는 설계도 같은 애라고 기억해두자

특히 readonly같은 키워드가 Interface에 있어도 클래스에서 해당 속성이나 메소드를 readonly 키워드를 붙이지 않아도 에러가 나지않는다. (당연히 readonly를 붙이지 않았으니 읽기전용이 되지도 않는다)