Classes
클래스를 사용하며 타입스크립트로 객체지향 프로그래밍을 해보자!
클래스 자체에 대해서는 딱히 설명하지 않을 예정이니 자바스크립트에서의 클래스를 우선 공부하면 좋다.
private / protected / public
class Player {
constructor(
private firstName: string,
protected lastName: string,
public nickName: string
) {}
}
const ryung = new Player("ryund", "lee", "이령");
// ryung.firstName // 에러
// Property 'firstName' is private and only accessible within class 'Player'.
// ryung.lastName // 에러
// Property 'lastName' is protected and only accessible within class 'Player' and its subclasses.
ryung.nickName = "령이"; // Player: { "nickName": "령이" };
private를 constructor 내에서 옵션을 선언할 때 사용하면 읽기 전용으로 취급되어 접근이 불가능하다.
대신에 public으로 선언된 옵션은 접근이 가능하다.
(아무 키워드도 작성하지 않는다면 public이 기본값으로 적용된다.)
proteced는 private와 public의 중간 단계로 위치에 따라 접근이 가능할 수도 불가능할 수도 있다.
각 접근 가능한 위치를 정리하자면 아래와 같다
선언한 클래스 내 (해당 클래스) |
상속받은 클래스 내 (자식 클래스) |
인스턴스 (모든 클래스) |
|
private | ⭕ | ❌ | ❌ |
protected | ⭕ | ⭕ | ❌ |
public | ⭕ | ⭕ | ⭕ |
위의 예시를 통해
private로 선언된 firstName과 protected로 선언된 lastName은 접근 시 에러가 발생하지만
public으로 선언된 nickName은 유일하게 접근 가능함을 볼 수 있다.
이 private 같은 키워드들은 타입스크립트에서 보호해주기 위해서만 사용되는 것이며
자바스크립트로 컴파일된 내용을 보면 private가 사라지는 모습을 볼 수 있다.
앞으로 나올 키워드도 오로지 타입스크립트의 보호장치들이라는 것을 참고하자.
+) readonly
접근은 가능하지만 수정은 불가능한, 읽기 전용으로 만들어주는 키워드
pulic readonly 키워드로 선언할 경우, 모든 곳에서 접근은 가능하더라도 수정은 불가능하도록 제어한다.
Abstract Class
추상클래스
다른 클래스가 상속 받을 수만 있는 클래스를 의미한다.
아래 예시를 통해 추상클래스 User를 알아보자.
abstract class User { // 추상클래스 User
constructor(
private firstName: string,
private lastName: string,
public nickName: string
) {}
}
class Player extends User { // 추상클래스 User를 상속하는 클래스 Player
...
}
// const ryung = new User( ... ); // 에러 : 직접 새로운 인스턴스를 바꿀 수 없다
const ryung = new Player("ryung", "lee", "이령");
abstract를 통해 추상클래스로 선언된 User를 클래스 Player가 extends 통해 상속 받은 모습이다.
그리고 추상클래스는 오로지 상속 받을 수만 있기 때문에 직접 새로운 인스턴스를 바꿀 수 없다.
때문에 const ryung = new User(...) 와 같이 작성한다면 오류가 나며
대신에 new Player(...)와 같이 상속받은 Player를 이용해 인스턴스를 바꾸는 것은 문제 없다.
추상클래스 안의 메소드
추상클래스 안에 메소드를 작성하는 방법은 다음과 같다.
abstract class User {
constructor(
private firstName: string,
private lastName: string,
public nickName: string
) {}
getFullName(){
return `${this.firstName} ${this.lastName}`;
}
}
class Player extends User { ... }
const ryung = new Player("ryung", "lee", "이령");
ryung.getFullName(); // ? "ryung lee"
User로부터 메소드 getFullName()도 상속받기 때문에, Player는 getFullName()을 그대로 사용할 수 있다.
참고로 앞서 배운 private는 property 외 메소드에서도 동작하기 때문에 private getFullName() 으로 선언 가능하다.
추상메소드 (abstract method)
추상클래스를 상속받는 모든 것들이 구현해야하는 메소드.
앞서 다룬 추상클래스 안의 메소드와는 다른 개념이다.
추상클래스 안에서 추상메소드 만들기 위해서는 메소드를 구현해서는 안 되고, 메소드의 call signature만 작성해야 한다.
abstract class User {
constructor(
private firstName: string,
private lastName: string,
protected nickName: string
) {}
abstract getNickName():void // 추상메소드 getNickName : call signature만 작성
}
class Player extends User {
getNickName() { // 상속받은 클래스에서 구현
console.log(this.nickName);
}
}
여기에서 nickName 선언 앞에 private는 사용하면 안된다.
private을 이용하면 자식클래스 Player에서 접근할 수 없어 this 를 이용하지 못하기 때문이다.
위에서는 자식클래스에서 this로 접근이 가능하도록 protected를 사용했고, public을 사용해도 된다.
실전 연습 : 해시맵 만들기
해싱 알고리즘을 쓰는 해시맵.... 쉽게 단어 사전을 만들어보자.
1) 객체 words 들을 가지는 사전을 만들고,
2) 단어를 추가하고, 찾아보고, 수정하고, 삭제하는 메소드를 만들자
type Words = {
[key: string]: string;
// object의 Type을 선언해야할 때 사용
// - 제한된 양의 property만 가질 수 있게 제한
// - property의 이름은 모르지만 Type만 알고있을 때 사용
};
// 단어 사전 Dict
class Dict {
// object 형식의 words 저장
private words: Words; // constructor가 없으면, initialize 관련 에러 발생
constructor() {
this.words = {}; // constructor에서 수동 초기화 진행해줌으로 에러 해결
}
add(word: Word) { // 클래스 World를 Type처럼 사용 가능
if (this.words[word.term] === undefined) {
this.words[word.term] = word.def;
}
}
def(term: string)
{return this.words[term];
}
update(word: Word) {
if (this.words[word.term] !== undefined) {
this.words[word.term] = word.def;
}
}
del(term: string) {
if (this.words[term] !== undefined) {
delete this.words[term];
}
}
}
// 각각의 단어 Word
class Word {
constructor(
public readonly term: string,
public readonly def: string
) {}
}
// --------------------------
const kimchi = new Word("kimchi", "super cool food");
const pizza = new Word("pizza", "super nice piazza");
const dict = new Dict();
dict.add(kimchi);
dict.add(pizza);
console.log(`KIMCHI: ${dict.def("kimchi")}`); // ? "KIMCHI: super cool food"
console.log(`PIZZA: ${dict.def("pizza")}`); // ? "PIZZA: super nice piazza"
dict.update(new Word("kimchi", "very incredible super food"));
console.log(`UPDATE KIMCHI: ${dict.def("kimchi")}`); // ? "UPDATE KIMCHI: very incredible super food"
console.log(`NOT UPDATE PIZZA: ${dict.def("pizza")}`); // ? "NOT UPDATE PIZZA: super nice piazza"
dict.del("pizza");
console.log(`DELETE PIZZA ${dict.def("pizza")}`); // ? "DELETE PIZZA undefined"
console.log(`NOT DELETE KIMCHI: ${dict.def("kimchi")}`); // ? "NOT DELETE KIMCHI: very incredible super food"
참고
'TYPESCRIPT' 카테고리의 다른 글
[TYPESCRIPT] npm run dev하면 저절로 해결되는 타입 에러? (env.d.ts) (0) | 2023.11.20 |
---|---|
[TYPESCRIPT] Type과 Interface의 차이점. 그리고 추상클래스 (0) | 2023.05.10 |
[TYPESCRIPT] Function Call Signatures (0) | 2023.05.04 |
[TYPESCRIPT] 타입스크립트 큰 맥락 파악하기 : 타입들 총 정리 (0) | 2023.05.03 |
[TYPESCRIPT] 추론적 타입과 명시적 타입 (0) | 2023.04.26 |