본문 바로가기

내일배움캠프_개발일지/TypeScript 연습

TypeScript 연습 <1> -9-

________________________________________________________________________

 

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