본문 바로가기
반응형

Class

타입스크립트에서는 객체 지향 프로그래밍을 지원하는 기능을 제공하고 있다. 추상 클래스와 상속을 통해 코드의 재사용성을 높이고, 구조화된 방식으로 프로그램을 설계할 수 있다. 접근 제한자를 사용하여 클래스의 속성과 메서드의 접근 범위를 제어할 수 있으며, 이를 통해 코드의 캡슐화를 구현할 수 있다.

TS 클래스 코드
JS와 다르게 추상 클래스에 대하여 속성과 메서드의 접근 범위를 제어할 수 있다.

실습하기

// 'Words' 타입 정의: 문자열 키와 문자열 값을 가지는 객체 타입
type Words = {
    [key: string]: string; // 인덱스 시그니처: 키는 문자열, 값은 문자열
};

// 'Dict' 클래스 정의
class Dict {
    private words: Words; // private 속성 'words', 'Words' 타입 사용
    constructor() {
        this.words = {}; // 빈 객체로 초기화
    }

    // 단어 추가 메서드
    add(word: Word) {
        // 'words' 객체에 단어가 없으면 추가
        if (this.words[word.term] === undefined) {
            this.words[word.term] = word.def;
        }
    }

    // 단어 정의 반환 메서드
    def(term: string) {
        // 'words' 객체에서 해당 단어의 정의 반환
        return this.words[term];
    }

    // 단어 정의 업데이트 메서드
    update(word: Word) {
        // 'words' 객체에 단어가 있으면 정의 업데이트
        if (this.words[word.term] !== undefined) {
            this.words[word.term] = word.def;
        }
    }

    // 단어 삭제 메서드
    del(term: string) {
        // 'words' 객체에서 단어가 있으면 삭제
        if (this.words[term] !== undefined) {
            delete this.words[term];
        }
    }
}

// 'Word' 클래스 정의
class Word {
    // 생성자: 'term'과 'def' 속성을 초기화
    constructor(public term: string, public def: string) { }
}

// 단어 객체 생성
const kimchi = new Word("kimchi", "Korean food");
const pizza = new Word("pizza", "piazza");

// 사전 객체 생성
const dict = new Dict();

// 단어 추가
dict.add(kimchi);
dict.add(pizza);

// 단어 정의 출력
console.log("KIMCHI:", dict.def("kimchi")); // "KIMCHI: Korean food"
console.log("PIZZA:", dict.def("pizza"));   // "PIZZA: piazza"

// 단어 정의 업데이트
dict.update(new Word("kimchi", "very incredible Korean food"));

// 업데이트된 단어 정의 출력
console.log("UPDATE KIMCHI:", dict.def("kimchi")); // "UPDATE KIMCHI: very incredible Korean food"
console.log("NOT UPDATE PIZZA:", dict.def("pizza")); // "NOT UPDATE PIZZA: 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 Korean food"

Interfaces

TypeScript에서는 인터페이스를 사용하여 객체의 구조를 정의하고, 클래스가 이를 구현하도록 강제할 수 있다.

반면, JavaScript에는 이러한 기능이 없고, 객체의 구조를 강제할 방법이 없다.

interface 코드
오른쪽의 JS 코드에서는 인터페이스에 대한 코드를 확인할 수 없다.

Interface와 type의 차이점

interface주로 객체의 구조를 정의하고 확장과 병합이 가능한 반면, type 다양한 형태의 타입을 정의할 수 있으며 유니언(| 타입을 사용)과 인터섹션 타입(& 타입을 사용) 표현이 가능하다. 

// 첫 번째 'Animal' 인터페이스 정의
interface Animal {
    name: string; // 'name' 속성: 동물의 이름
}

// 두 번째 'Animal' 인터페이스 정의 (병합)
interface Animal {
    age: number; // 'age' 속성: 동물의 나이
}

// 세 번째 'Animal' 인터페이스 정의 (병합)
interface Animal {
    species: string; // 'species' 속성: 동물의 종
}

// 'Animal' 인터페이스를 구현하는 클래스
class Dog implements Animal {
    constructor(
        public name: string, // 'name' 속성 초기화
        public age: number,  // 'age' 속성 초기화
        public species: string // 'species' 속성 초기화
    ) { }

    // 메서드: 'Dog' 객체의 정보를 출력
    getInfo() {
        return `${this.name} is a ${this.age}-year-old ${this.species}.`;
    }
}

// 'Animal' 인터페이스를 사용하는 객체 생성
const myDog: Animal = {
    name: "Buddy",     // 이름 설정
    age: 5,            // 나이 설정
    species: "Labrador" // 종 설정
};

// 'Dog' 클래스의 인스턴스 생성
const buddy = new Dog("Buddy", 5, "Labrador");

// 정보 출력
console.log(buddy.getInfo()); // "Buddy is a 5-year-old Labrador."

// 인터페이스 병합 결과 확인
console.log(myDog); // { name: "Buddy", age: 5, species: "Labrador" }

Polymorphism(다형성)

제네릭을 사용한 다형성은 다양한 타입에 대해 동일한 로직을 적용할 수 있게 도와주는데, 아래 코드에서는 LocalStorage 클래스가 제네릭을 사용하여 문자열, 불리언 등 다양한 타입의 데이터를 저장, 읽기, 업데이트, 삭제할 수 있는 유연한 구조를 제공하고 있다. TS에서는 제네릭을 통해 class에 대해서도 다형성을 통해 코드의 재사용성, 타입 안전성, 유연성을 높일 수 있다. 

// 제네릭 인터페이스 SStorage 정의: 키는 문자열, 값은 제네릭 타입 T
interface SStorage<T> {
    [key: string]: T; // 인덱스 시그니처: 모든 키는 문자열이고, 값은 타입 T
}

// 제네릭 클래스 LocalStorage 정의: 제네릭 타입 T 사용
class LocalStorage<T> {
    // SStorage<T> 타입의 storage 속성 초기화
    private storage: SStorage<T> = {};

    // Create: 새로운 항목을 추가
    set(key: string, value: T) {
        if (this.storage[key] !== undefined) {
            // 키가 이미 존재하면 메시지 출력
            return console.log(`${key}가 이미 존재합니다. update 호출 바랍니다.`);
        }
        // 키가 존재하지 않으면 새로운 값 저장
        this.storage[key] = value;
    }

    // Read: 키에 해당하는 값을 반환
    get(key: string): T | void {
        if (this.storage[key] === undefined) {
            // 키가 존재하지 않으면 메시지 출력
            return console.log(`${key}가 존재하지 않습니다.`);
        }
        // 키가 존재하면 해당 값 반환
        return this.storage[key];
    }

    // Update: 기존 항목을 업데이트, 존재하지 않으면 새로 추가
    update(key: string, value: T) {
        if (this.storage[key] !== undefined) {
            // 키가 존재하면 값 업데이트
            this.storage[key] = value;
        } else {
            // 키가 존재하지 않으면 메시지 출력 후 새로 추가
            console.log(`${key}가 존재하지 않아 새로 만듭니다.`);
            this.storage[key] = value;
        }
    }

    // Delete: 키에 해당하는 값을 삭제
    remove(key: string) {
        if (this.storage[key] === undefined) {
            // 키가 존재하지 않으면 메시지 출력
            return console.log(`${key}가 존재하지 않습니다.`);
        }
        // 키가 존재하면 해당 값 삭제
        delete this.storage[key];
    }

    // Clear: 모든 항목 삭제
    clear() {
        this.storage = {}; // storage 객체 초기화
    }
}

// 문자열을 저장할 수 있는 LocalStorage 인스턴스 생성
const stringsStorage = new LocalStorage<string>();

// 키 "asdf"에 해당하는 값 읽기 시도 (값이 없으므로 메시지 출력)
stringsStorage.get("asdf");

// 키 "asdf"에 문자열 "asdfasf" 저장
stringsStorage.set("asdf", "asdfasf");

// 불리언을 저장할 수 있는 LocalStorage 인스턴스 생성
const booleanStorage = new LocalStorage<boolean>();

// 키 "asdf"에 불리언 값 false 저장
booleanStorage.set("asdf", false);

class + generic
JS에서는 제네릭에 따른 타입에 대한 구분을 확인할 수 없다.

반응형