코딩/Typescript

타입스크립트 class visibility - 타입스크립트 TypeScript 강좌 12편

드리프트 2021. 12. 12. 20:16
728x170

 

 

안녕하세요?

 

오늘은 타입스크립트의 class에 대해 알아 보겠습니다.

 

class는 다른 언어에서도 제공하고 있고 예전에는 객체지향 프로그래밍이 아주 유행했었는데요.

 

C++ 의 Qt 나 윈도우즈의 MFC 라이브러리 모두 class를 이용한 결과입니다.

 

class는 사용하면 아주 강력한 기능이 될 수 도 있는데요.

 

유지 보수가 어려워 그냥 함수형 프로그래밍 같이 직관적인 프로그래밍이 요즘 대세가 되었습니다.

 

본인도 class는 React 프로그래밍에서는 잘 안 쓰는데요.

 

그래도 타입스크립트에서 제공하는 class 기능을 잠깐 맛보기로 알아 보도록 하겠습니다.

 

 

예제를 들어 설명할까 하는데요.

 

간단한 데이터베이스 DB를 class로 만들어 볼까요?

 

interface Database {
    get(id: string): string;
    set(id: string, value: string): void;
}

 

인터페이스 Database를 선언했습니다.

 

get과 set 함수가 있네요.

 

그럼 이 Database 인터페이스를 구현하는 class를 만들어 보도록 하겠습니다.

 

class InMemoryDatabase implements Database {
    db: Record<string, string> = {};
    
    get(id: string): string {
        return this.db[id];
    }
    set(id: string, value: string): void {
        this.db[id] = value;
    }
}

 

인터페이스 Database를 구현(implements) 했기 때문에 위와 같이 InMemoryDatabase는 get, set 함수를 실제 코드로 구현해야 합니다.

 

그래서 위와 같이 get, set 함수를 작성했는데요.

 

우리가 여기서 볼 변수는 db 라는 class 멤버 변수 입니다.

 

Record 타입의 유틸리티 타입을 이용했는데요.

 

const myDB = new InMemoryDatabase;

myDB.set("foo", "bas");

console.log(myDB.get("foo"));

위와 같이 예제를 만들어서 실행해 볼까요?

 

 

실행이 아주 잘 되네요.

 

여기서 클래스의 캡슐화에 대해 알아 볼건데요.

 

만약 다음과 같은 코드가 있다면 어떻게 될까요?

 

const myDB = new InMemoryDatabase;

myDB.set("foo", "bas");

myDB.db["foo"] = "baz";

console.log(myDB.get("foo"));

 

 

클래스 멤버 변수인 db에 직접 연결하여 값을 수정했네요.

 

이런 방식으로는 클래스를 사용하는 의미가 전혀 없습니다.

 

그래서 클래스는 멤버 접근 지정자를 제공하는데요.

 

private, protected, public 이렇게 3가지가 있습니다.

 

멤버 변수 앞에 위와 같은 멤버 접근 지정자를 지정할 수 있는데요.

 

지정 안하면 자동으로 public이 됩니다.

 

public은 아무나 클래스 멤버에 접근할 수 있다는 뜻입니다.

 

그러면 private와 protected의 차이는 뭘까요?

 

둘 다 클래스 멤버에 접근할려면 클래스 내부에서 접근해야 한다는 뜻인데요.

 

private는 해당 클래스에서만 접근가능하다는 가장 강력한 지정자입니다.

 

반면에 protected는 상속된 자식 클래스에서 접근 할 수 있다는 뜻입니다.

 

 

그러면 위 코드에서

myDB.db["foo"] = "baz";
 
위 처럼 강제로 클래스 멤버를 수정못하게 해볼까요?
 
 
class InMemoryDatabase implements Database {
    private db: Record<string, string> = {};
    get(id: string): string {
        return this.db[id];
    }
    set(id: string, value: string): void {
        this.db[id] = value;
    }
}

 

db 변수 앞에 private 멤버 접근 지정자를 추가했습니다.

 

그러면 인텔리센스가 다음과 같이 보여줍니다.

 

 

위와 같이 클래스 멤버를 외부에서 접근 못하도록 막고 있습니다.

 

이제 우리의 클래스는 private 한 db 멤버 변수를 가지고 있고 이 변수는 오직 클래스 내부에서만 접근 가능합니다.

 

그러면 protected 같은 경우 어떻게 작도하는지 예제를 통해 알아볼까요?

 

위에서 만든 클래스를 상속하기 위해 다른 인터페이스를 추가해 보도록 하겠습니다.

 

interface Database {
    get(id: string): string;
    set(id: string, value: string): void;
}

interface Persistable {
    saveToString(): string;
    restoreFromString(storedState: string): void;
}

 

처음에 만들었던 Database 인터페이스 외에 Persistable 인터페이스를 추가했습니다.

 

Persistable 인터페이스는 DB를 저장하고 또는 저장된 값을 불러오는 역할을 하는데요.

 

그럼 클래스를 만들어 볼까요?

 

우리가 만들려고 하는 클래스는 위에서 만든 InMemoryDatabase 를 상속합니다.

 

타입스크립트에서 상속은 다음과 같이 하면 됩니다.

 

class PersistentMemoryDB extends InMemoryDatabase {

}

이제 PersistentMemoryDB는 InMemoryDatabse 클래스를 상속하게 됩니다.

 

그럼 우리가 위에서 만든 Persistable 인터페이스는 어떻게 연결할까요?

 

class PersistentMemoryDB extends InMemoryDatabase implements Persistable {

}

 

그냥 마지막에 implements Persistable 이라고 추가하면 됩니다.

 

자, 이제 우리의 PersistentMemoryDB는 InMemoryDatabase를 상속했기 때문에 InMemoryDatabase의 모든 멤버 변수나 함수를 이용할 수 있습니다.

 

그리고 PersistentMemoryDB는 따로 Persistable 를 구현했기 때문에 우리는 아래 처럼  인터페이스 Persistable 를 직접 코드로 구현해야 합니다.

 

class PersistentMemoryDB extends InMemoryDatabase implements Persistable {
    saveToString(): string {
        return JSON.stringify(this.db)
    }
    restoreFromString(storedState: string): void {

    }
}

 

 

코드를 작성하다가 인텔리센스가 에러를 보여주는데요.

 

무슨 에러일까요?

 

this.db 에서 db는 InMemoryDatabase 클래스에서 private으로 선언했기 때문입니다.

 

private로 선언하면 자식 클래스에서도 볼 수 없게 되기 때문입니다.

 

그럼 어떻게 해야 할까요?

 

바로 InMemoryDatabase에서 멤버 변수 db를 protected로 바꿔주면 됩니다.

 

class InMemoryDatabase implements Database {
    protected db: Record<string, string> = {};
    get(id: string): string {
        return this.db[id];
    }
    set(id: string, value: string): void {
        this.db[id] = value;
    }
}

class PersistentMemoryDB extends InMemoryDatabase implements Persistable {
    saveToString(): string {
        return JSON.stringify(this.db)
    }
    restoreFromString(storedState: string): void {
        this.db = JSON.parse(storedState)
    }
}

 

이제 에러없이 우리가 원하는 클래스를 만들었습니다.

 

이제 테스트해볼까요?

 

const myDB = new PersistentMemoryDB;

myDB.set("foo", "bas");

console.log(myDB.get("foo"));
console.log(myDB.saveToString());

 

아주 잘 실행되고 있습니다.

 

좀더 복잡한 예를 들어 볼까요?

 

const myDB = new PersistentMemoryDB;

myDB.set("foo", "bas");

console.log(myDB.get("foo"));
console.log(myDB.saveToString());

const saved = myDB.saveToString();

const myDB2 = new PersistentMemoryDB;

myDB.set("foo", "baz");

myDB2.restoreFromString(saved);

console.log(myDB.get("foo"));
console.log(myDB2.get("foo"));

 

myDB.saveToString() 함수와 restoreFromString() 함수가 잘 작동되고 있네요.

 

이상 클래스의 멤버 접근자에 대해 알아 봤습니다.

 

그리드형