/ TYPESCRIPT

TypeScript 강좌(7) - Class

TypeScript 강좌는 여러 절로 구성되어 있습니다.


TypeScript Class

기존의 JavaScript는 재사용 가능한 component를 만들기 위해 함수와 prototype에 기반한 상속을 이용했습니다. 하지만 기존 객체지향에 익숙해 있는 개발자들에게는 상당히 생소한 개념이었죠. 그래서 ECMAScript 2015( ES6 )에서는 개발자들이 좀 더 쉽게 JavaScript Application을 구현할 수 있도록 전통적인 class 기반의 객체지향 개념을 도입했습니다.

TypeScript 역시 이 class기반의 객체지향 기법을 이용해 Application을 개발할 수 있습니다.

일단 ECMAScript 2015( ES6 )의 class는 다음과 같이 정의하고 사용할 수 있습니다.

class Book {

    constructor(btitle,bauthor) {
        this.btitle = btitle;
        this.bauthor = bauthor;
    }

    printInfo() {
        console.log(`제목: ${this.btitle}, 저자: ${this.bauthor}`);
    }
}

let book = new Book('젊은 베르테르의 슬픔','괴테');
book.printInfo();

위의 코드는 data type의 정보를 포함하고 있지 않기 때문에 TypeScript로 변형하면 오류가 발생합니다. 적절히 타입 정보를 포함해 코드를 수정하면 다음과 같습니다.

class Book {

    btitle: string;
    bauthor: string;

    constructor(btitle:string, bauthor:string) {
        this.btitle = btitle;
        this.bauthor = bauthor;
    }

    printInfo(): void {
        console.log(`제목: ${this.btitle}, 저자: ${this.bauthor}`);
    }
}

let book:Book = new Book('젊은 베르테르의 슬픔','괴테');
book.printInfo();

위의 코드는 Java에서 우리가 익히 보아왔던 class의 형태입니다. 생성자를 표현하는 부분이 좀 생소하지만 쉽게 이해할 수 있을 듯 합니다.


Inheritance

일반적인 객체지향언어의 Inheritance 개념 역시 TypeScript에도 사용할 수 있습니다. 다음의 코드를 보죠.

class Book {

    btitle: string;
    bauthor: string;

    // 상위 클래스의 생성자
    constructor(btitle:string, bauthor:string) {
        this.btitle = btitle;
        this.bauthor = bauthor;
    }

    // 상위 클래스의 method
    // 입력 인자가 있으면 사용하고 없으면 default 사용
    printInfo(input:string = 'Initial'): void {
        console.log(input);
        console.log(`제목: ${this.btitle}, 저자: ${this.bauthor}`);
    }
}

// class의 상속
class EBook extends Book {

    btype: string;

    constructor(btitle:string, bauthor:string, btype:string) {
        // 상위 class 생성자 호출
        super(btitle, bauthor);
        this.btype = btype;
    }

    // method overriding
    printInfo(): void {
        // 상위 class의 method 호출
        super.printInfo();
        console.log(`제목: ${this.btitle}, 
                     저자: ${this.bauthor},
                     타입: ${this.btype}`);
    }
}

// IS-A relationship에 의한 상위 class type 사용
let book:Book = new EBook('젊은 베르테르의 슬픔','괴테',
    'PDF');

// dynamic binding에 의한 overriding method 호출.
book.printInfo();

기존 class를 확장하여 새로운 class를 정의하는 방법입니다. IS-A Relationship 역시 성립합니다. 그로 인한 상위 타입으로 객체를 사용할 수 있습니다. 또한 위의 예에서 처럼 method overriding의 개념 역시 존재하고 dynamic binding 개념 역시 존재합니다. 물론 TypeScript에서는 공식적으로 저 용어를 사용하지는 않습니다. 다만 우리가 Java언어에서 알고 있던 객체지향 개념이 그대로 TypeScript에도 일부 적용된다고 보시면 됩니다.


Access Modifier

TypeScript는 3가지 종류의 접근제어 연산자를 제공합니다. 우리에게 익숙한 public, protected, private 키워드로 제공되며 default값은 public입니다. 즉, Access Modifier를 명시하지 않으면 모두 public으로 간주됩니다.

  • public : 접근제한이 없습니다. class외부에서 자유롭게 접근이 가능합니다.
  • protected : class 외부에서 접근할 수 없습니다. 단, 상속받은 하위 class에서는 접근이 가능합니다.
  • privatre : class 외부에서 접근할 수 없습니다. 상속받은 하위 class에서도 접근이 불가능합니다.
class Book {

    protected btitle: string;

    public constructor(btitle:string, private _bauthor:string) {
        this.btitle = btitle;
    }

    public printInfo(): void {
        console.log(`제목: ${this.btitle}, 저자: ${this._bauthor}`);
    }

    // private property인 _bauthor의 getter
    get bauthor(): string {
        return this._bauthor;
    }

    // private property인 _bauthor의 setter
    set bauthor(value: string) {
        this._bauthor = value;
    }
}

class EBook extends Book {

    private btype: string;

    public constructor(btitle:string, bauthor:string, btype:string) {
        super(btitle, bauthor);
        this.btype = btype;
    }

    public printInfo(): void {
        console.log(`제목: ${this.btitle}, 
                     저자: ${this.bauthor},
                     타입: ${this.btype}`);
    }
}

let book:Book = new EBook('젊은 베르테르의 슬픔','괴테',
    'PDF');

book.printInfo();

위의 예제에서는 다음의 코드를 주의해서 보아야 합니다. 생성자의 인자로 private _bauthor:string라고 선언된 부분이 보입니다.

    protected btitle: string;

    public constructor(btitle:string, private _bauthor:string) {
        this.btitle = btitle;
    }

생성자에 인자를 명시할 때 access modifier를 같이 명시하면 위의 예처럼 명시적으로 해당 property가 선언되어 사용할 수 있게 됩니다.

일반적으로 private property의 이름은 앞에 _를 관용적으로 써주게 됩니다. 이렇게 사용하는 이유는 관용적 coding convention 때문에 그렇습니다. ECMAScript에서는 개발자들이 private의 의미로 사용되는 property에 일반적으로 _를 붙여서 사용했었습니다.

또 다른 이유는 settergetter의 이름때문에 그렇습니다. 다음의 코드에서 보듯이 TypeScript에서 getter와 setter의 표현은 우리가 알고 있는 다른 언어의 getter, setter와 다릅니다. 그리고 이렇게 선언된 getter와 setter가 어떻게 사용되는지도 유의해서 보시기 바랍니다.

    // private property인 _bauthor의 getter
    get bauthor(): string {
        return this._bauthor;
    }

    // private property인 _bauthor의 setter
    set bauthor(value: string) {
        this._bauthor = value;
    }

Readonly Property

class의 property를 readonly로 지정할 수 있습니다. readonly로 지정되면 property가 선언될 때 혹은 생성자안에서 반드시 초기화를 진행해야 합니다. 다음의 예제를 참고하시면 됩니다.

class Book {

    public readonly btitle: string;

    constructor(btitle: string) {
        this.btitle = btitle;
    }

}

let book:Book = new Book('젊은 베르테르의 슬픔');

book.btitle = '파우스트';   // 코드 에러

다음의 예제처럼 생성자의 parameter를 readonly로 선언하면 따로 class의 property로 선언할 필요가 없습니다.

class Book {

    constructor(readonly btitle: string) {
        this.btitle = btitle;
    }

}

let book:Book = new Book('젊은 베르테르의 슬픔');

console.log(book.btitle);

Static Property

static 키워드 역시 사용할 수 있습니다. ECMAScript 2015에서는 static을 method에만 적용할 수 있었지만 TypeScript는 property에도 적용할 수 있습니다. static property는 class의 이름으로 직접 access를 할 수 있습니다.

class Book {

    public btitle:string;
    static count: number;

    constructor(btitle: string) {
        this.btitle = btitle;
        Book.count++;
    }

}

let book1:Book = new Book('젊은 베르테르의 슬픔');
let book2:Book = new Book('파우스트');

console.log(Book.count);

Abstract Class

abstract class는 하나이상의 abstract method를 가지고 있는 class를 의미합니다. method의 선언만을 가지고 있기 때문에 직접적인 객체생성을 할 수 없고 상속을 이용해 하위 클래스에서 abstract method를 overriding해서 사용하게 됩니다.

abstract class Book {

    public btitle:string;

    constructor(btitle: string) {
        this.btitle = btitle;
    }

    abstract printInfo(): void;
}

class EBook extends Book {

    printInfo(): void {
        console.log(this.btitle);
    }
}

let book:Book = new EBook('젊은 베르테르의 슬픔');
book.printInfo();

interface의 의미로 class사용

이 부분은 기존의 다른 언어와 다릅니다. class를 확장해서 interface를 정의할 수 있습니다. 다음의 코드를 참조하세요

class Book {

    btitle: string;

}

interface EBook extends Book {

    bauthor: string;
}

let book:EBook = {
    btitle: '파우스트',
    bauthor: '괴테'
};

이번 포스트에서는 TypeScript의 class에 대해서 살펴보았습니다. 객체지향 언어의 class와 상당히 유사하면서도 약간의 차이점이 있었습니다. 차이점 위주로 기억하시면 될 듯 합니다.

End.


이 포스트의 내용은 아래의 사이트를 참조했습니다. 조금 더 자세한 사항을 알고 싶으시면 해당 사이트를 방문하세요!!