________________________________________________________________________
TypeScript 로 객체 지향 프로그래밍 -1- Class
< 타입스크립트로 객체 지향 프로그래밍 하기. 클래스와 인터페이스 - 클래스 >
타입스크립트는 기존의 자바스크립트 대비 클래스, 인터페이스 같은 객체 지향 기술을 보다 더 멋지고 효과적으로 구현할 수 있어.
=> 사실 C# 이나 java 를 배웠다면 식은 죽 먹기지. 왜? 타입스크립트 이거 거의 자바 그대론데…
1.
________________________________
< 타입스크립트로 클래스를 작성 >
——
class Player5 {
constructor (
private firstName: string,
private lastName: string,
public nickname: string
) {
}
}
——
=> 멋지지?
눈여겨 볼 게 2가지 있어.
첫 번째는, 생성자 함수에서 인자로 변수를 받아서 타입만 정해줘도, this.firstName = firstName 이걸 알아서 처리해준 다는 것.
저걸로 끝이야. 저렇게 하면 Player5 클래스의 프로퍼티가 된 거야.
——
class Player5 {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
——
이것과 똑같은 거지.
두 번째는, 프로퍼티에게 private 와 public 을 부여할 수 있다는 것.
물론 타입스크립트도 결국에는 자바스크립트로 트랜스파일이 되기 때문에 최종적으로는 효력이 없는 키워드 이지만,
개발 단계에서는 확실하게 private, public 역할을 해 주기 때문에 괜찮아.
——
class Player5 {
constructor (
private firstName: string,
private lastName: string,
public nickname: string
) {}
}
const msdou49 = new Player5("minsoo", "kim", "lolo");
msdou49.firstName // (property) Player5.firstName: string
// Property 'firstName' is private and only accessible within class 'Player5'
——
=> 실제로 private 키워드가 붙은 프로퍼티를 외부에서 호출하려 하면 오류가 뜨면서 막아버려.
2.
________________________________
< 추상 클래스 ( Abstrack Class ) >
객체 지향 언어들이 가지고 있는 장점이야 여러 가지가 있겠지만, 그 중 가장 으뜸 가는 것 중 하나가 바로 추상 클래스.
추상 클래스는 다른 클래스가 상속받을 수 있는 클래스.
하지만, 추상 클래스는 직접 새로운 인스턴스를 만들 수는 없어. 단지 다른 하위 클래스들에게 상속(exptends) 될 뿐이지.
——
abstract class User {
constructor (
private firstName: string,
private lastName: string,
public nickname: string
) {}
}
class Player5 extends User{
}
const msdou49 = new Player5("minsoo", "kim", "lolo");
// 신기하네? super() 를 사용하지 않아도 extends 만으로 바로 부모의 프로퍼티들을 사용할 수 있다니.
——
=> 기존의 Player5 안에 있던 생성자 함수를 새로 만든 abstract class User 로 옮기고,
Player5 는 User 클래스를 상속받게 만들었어.
추상 클래스는 자기 혼자서는 인스턴스를 만들 수 없어.
——
const msdou49 = new User("minsoo", "kim", "lolo"); // Cannot create an instance of an abstract class.
——
=> 위와 같이 에러를 띄우며 빨간줄이 그여.
3.
________________________________
< 추상 클래스 안의 메소드와 추상 메소드(Abstract method) >
다음과 같이 추상 클래스 안에 메소드가 있다고 가정.
——
abstract class User {
constructor (
private firstName: string,
private lastName: string,
public nickname: string
) {}
getFullName () {
return `${this.firstName} ${this.lastName}}`
}
}
——
=> 추상 클래스 안에 존재하는 getFullName()
추상 클래스 내부에서 메소드가 선언되었음으로, 해당 클래스를 상속받는 클래스는 당연히 getFullName() 메소드를 사용할 수 있어.
——
class Player5 extends User{
}
const msdou49 = new Player5("minsoo", "kim", "lolo");
msdou49.getFullName();
——
=> 아무런 에러도 발생하지 않아.
물론 User 의 getFullName 에 private 가 선언된다면? 아쉽게도 msdou49 에서는 해당 메소드를 참조할 수 없게 되겠지.
즉, private, public 은 property 뿐 아니라 메소드에도 적용된다.
어쨌든 추상 클래스 안에 getFullName 라는 메소드가 있는데,
우린 이 getFullName 를 ‘추상 메소드’ 로 만들 수 있어.
추상 클래스 안에서는 추상 메소드를 만들 수 있어.
단, 단순히 메소드를 구현해서는 안 되고, 메소드의 call signature 만 적어줘야 해.
——
abstract class User {
constructor (
private firstName: string,
private lastName: string,
private nickname: string
) {}
abstract getNickName(): void // call signature
getFullName () {
return `${this.firstName} ${this.lastName}}`
}
}
class Player5 extends User{ // Player5 에 빨간 밑줄이 그여.
// Non-abstract class 'Player5' does not implement inherited abstract member 'getNickName' from class 'User'.
}
——
=> User 클래스 안에서 추상 메소드를 선언하니,
해당 클래스를 상속 받는 Player5 에서 오류가 발생해. getnickName 을 구현해야 된다, 하면서 말이야.
이를 통해서 추상 메소드가 무엇인지를 알 수 있어.
추상 메소드는 추상 클래스를 상속받는 모든 것들이 구현을 해야하는 메소드.
애당초 signature 만을 제공하고 있기에 구체적으로 어떻게 구현 될지는 각 클래스들이 알아서 하는 것이지만,
어쨌든 구현은 무조건 해야 하는 거야. 그게 추상 메소드.
4.
________________________________
< 추상 클래스 안의 메소드와 추상 메소드(Abstract method) - 하위 클래스에서 부모 클래스의 추상 메소드 구현하기 >
——
class Player5 extends User{
getNickName () {
console.log(this.firstName)
}
}
const msdou49 = new Player5("minsoo", "kim", "lolo");
msdou49.getFullName();
——
=> 그래서 User 추상 클래스를 상속 받은 Player5 안에서 getNickName 를 구현하고,
콘솔 로그로 User 의 내부 프로퍼티 ( firstName, lastName, nickname ) 를 호출하려 하지만 실패해.
왜?
내가 만약 클래스의 property 를 private 으로 만든다면, 해당 클래스를 다른 하위 클래스가 상속받을 지언정
private 속성에는 접근할 수가 없어. getFullName 는 private 이 아니기 때문에 바깥에서도 호출이 가능한 것이고.
이는 클래스 내부 프로퍼티의 접근 권한을 설정하는 키워드가 private, public 뿐 만 아니라 다른 것도 있어서 이래.
바로 protected.
*** 만약 외부로부터는 보호받고 싶지만 다른 자식 클래스에서는 사용되기를 원한다면, private 는 사용하면 안 돼.
——
abstract class User {
constructor (
private firstName: string,
private lastName: string,
protected nickname: string
) {}
abstract getNickName(): void
getFullName () {
return `${this.firstName} ${this.lastName}}`
}
}
class Player5 extends User{
getNickName () {
console.log(this.nickname); // protected 라서 User 외부에서 호출해도 문제 없어.
}
}
const msdou49 = new Player5("minsoo", "kim", "lolo");
msdou49.getFullName();
msdou49.getNickName();
——
=> protected 키워드를 사용하면 여전히 Private 처럼 외부로부터는 안전하게 보호를 받지만,
자신의 자식 클래스들에게는 접근할 수 있도록 허락해 주고 있어.
getFullName() 의 경우엔 호출만 하는 거고 해당 메소드는 User 내부에서 참조하는 것이니 프로퍼티들이 private 여도
함수는 외부에서 잘 구동돼.
5.
________________________________
< 실전 연습. 여태껏 배운 것들을 활용하여 해시맵 만들어보자 - 사전 지식 파악하기. >
TypeScript는 기본적으로 객체의 프로퍼티를 읽을 때, string타입의 key 사용을 허용하지 않는다.
TypeScript가 처음이라면 아래 코드가 컴파일 에러를 만든다는 사실에 충격을 먹을 수도 있다.
——
const obj = {
foo: "hello",
}
let propertyName = "foo"
console.log(obj[propertyName]) // compile error!
——
=> 에러인 즉슨,
propertyName 는 string 타입이라서 객체의 프로퍼티 키를 호출할 수 없다고.
위 코드를 컴파일할 때 에러가 발생한 이유는 string literal 타입만 허용되는 곳에 string 타입을 사용했기 때문.
——
const a = "Hello World" // “Hello World” 타입. 스트링 리터럴
let b = "Hello World" // string 타입.
const c: string = "Hello World" // string 타입.
——
=> b는 let 이라서 얼마든지 다른 문자열로 바뀔 가능성이 있기에 string 타입이라고 추론.
c 는 아예 string 이라고 타입을 명시 했기 때문에 string 타입.
a의 경우, 컴파일러는 이 변수를 string이 아닌 조금 더 좁은 타입(narrowed type)으로 선언한 것으로 추론.
참고 : https://soopdop.github.io/2020/12/01/index-signatures-in-typescript/
해결 방법이 여러 가지 있는데, 그 중 하나가 Index Signature 선언하기.
[index: string]: string >> 이런 식으로 아예 처음부터 키를 string 타입으로 사용하도록 명시를 해주는 것.
6.
________________________________
< 나만의 사전 ‘해시맵’ 만들어보기 - 셋팅 >
——
type Words = { // Words 타입이 string 만을 property 로 가지는 오브젝트라는 것을 말해줌.
[key:string]: string // 제한된 양의 property 혹은 key 를 가지는 타입을 정의해 주는 방법.
// 객체의 키는 string 이어야 하고, 키값도 string 이어야 한다 라는 의미. key 라는 이름은 다른 것이여도 상관 없어.
// 다시. [key:string] 은 object의 type 을 선언 해야할 때 쓸 수 있어.
// [key:string] 는 프로퍼티의 이름=키 이름 에 대해서는 잘 알지 못하지만, 키 이름의 타입이 string 이라는 걸 알 때에 사용하는 방식.
// 물론 string 이 아니라 number 일 수도 있어.
}
class Dict {
private words: Words
constructor () {
this.words = {} // words 를 수동으로 초기화. 이렇게 안하면 이니셜라이즈=초기화 안했다고 오류 뜸.
}
}
class Word {
constructor (
public term: string,
public def : string
) {}
}
——
7.
________________________________
<나만의 해시맵 사전 구현>
——
type Words = { // Words 타입이 string 만을 property 로 가지는 오브젝트라는 것을 말해줌.
[key:string]: string // 제한된 양의 property 혹은 key 를 가지는 타입을 정의해 주는 방법.
// 객체의 키는 string 이어야 하고, 키값도 string 이어야 한다 라는 의미. key 라는 이름은 다른 것이여도 상관 없어.
// 다시. [key:string] 은 object의 type 을 선언 해야할 때 쓸 수 있어.
// [key:string] 는 프로퍼티의 이름=키 이름 에 대해서는 잘 알지 못하지만, 키 이름의 타입이 string 이라는 걸 알 때에 사용하는 방식.
// 물론 string 이 아니라 number 일 수도 있어.
}
class Dict {
private words: Words // 종합 사전 틀. private words: { [key:string]: string } 와 동일.
constructor () {
this.words = {} // words 를 수동으로 초기화. 이렇게 안하면 이니셜라이즈=초기화 안했다고 오류 뜨더라.
}
add (word: Word) { // 인자의 타입이 클래스!
if (this.words[word.term] === undefined) { // 해당 단어가 사전에 등록되어 있지 않을 경우 단어를 등록.
this.words[word.term] = word.def;
}
}
def (term: string) { // 사전에 내가 찾는 단어가 있는지 없는지 검색.
return this.words[term];
}
}
class Word {
constructor (
public term: string,
public def : string
) {}
}
const kimchi2 = new Word("kimchi", "한국의 음식");
const dict = new Dict();
dict.add(kimchi2); // Word 인스턴스로 사전 등록.
dict.def("kimchi"); // 입력할 때 string 타입으로 입력되는건가? 매개변수에 직접 문자열을 입력하면 string 타입인가봐. 리터럴이 아니라.
——
=>
눈 여겨 볼 점이 크게 3 가지. ********
1) [key:string]: string // 객체의 키 이름을 [“”] 로 조회할 때는 기본적으로 타입이 string literal.
이거를 첨부터 string 타입을 사용하겠다고 선언.
2) add (word: Word) // 인자로 받는 변수의 타입이 클래스. 정확히는 클래스 인스턴스가 들어오는 것.
3) this.words = {} // 클래스의 기본 프로퍼티를 초기화
*****
특히 2번이 중요.
여태까지 우리는 매개변수의 타입에 concrete, generic 은 사용해 봤지만 class 는 처음 사용해 봤어.
아래는 혼자서 몇 가지 추가.
——
type Words = {
[key:string]: string
}
class Dict {
private words: Words
constructor () {
this.words = {}
}
add (word: Word) {
if (this.words[word.term] === undefined) {
this.words[word.term] = word.def;
}
}
def (term: string) {
return this.words[term];
}
del (term: string) {
if (this.words[term]) {
delete this.words[term];
}
}
put (word: Word) {
if (this.words[word.term] === undefined) {
return
}
this.words[word.term] = word.def;
}
}
class Word {
constructor (
public term: string,
public def : string
) {}
}
const kimchi2 = new Word("kimchi", "한국의 음식");
const dict = new Dict();
dict.add(kimchi2);
dict.def("kimchi");
——
'내일배움캠프_개발일지 > TypeScript 연습' 카테고리의 다른 글
TypeScript 연습 <1> -11- (0) | 2023.02.02 |
---|---|
TypeScript 연습 <1> -10- (2) | 2023.02.01 |
TypeScript 연습 <1> -8- (0) | 2023.01.30 |
TypeScript 연습 <1> -7- (0) | 2023.01.30 |
TypeScript 연습 <1> -6- (0) | 2023.01.27 |