class

Classes

What a Class

object를 만드는 설계도이다. ex6 이전에는 function을 사용해서 object를 만들었다. 오브젝트는 new 키워드를 사용해서 만들 수 있고 타입스크립트를 사용하면 oop에 맞게 클래스를 작성할 수 있다. 타입스크립트에서는 class 자체도 어떤 타입이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person {
name: string = "Mark";
age: number;

constructor(age?: number) {
if (age === undefined) {
this.age = 20;
} else {
this.age = age;
}
}
}

const p1 = new Person(39);
const p2 = new Person();
console.log(p1);
console.log(p2);

생성자를 통해서 인수를 받을 수 있다. TS에서는 생서자 오버라이드도 지원하지만 생성자에서 받을수도 있고 안받을 수 도 있는 경우엔 ?를 사용해서 생성자를 작성한다.
이때 받는 인수에 타엡에 대한 처리를 따로 해주어야 한다.

생성자는 async를 사용할 수 없다. async 를 사용하기 위해서는 생성자 이외에 함수를 만들어서 async 키워드를 사용해야 한다. 이럴때 클래스에서는 멤버 변수가 할당됬는지 아닌지 알 수 없기 때문에 age!: number 같이 !를 사용한다.

Access Modifiers

클래스 외부에서 접근하는 것을 막기 위한 접근 제어자를 타입스크립트에서는 지원한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person {
public name: string = "Mark";
private _age!: number;

public constructor(age?: number) {
if (age === undefined) {
this._age = 20;
} else {
this._age = age;
}
}
}

const p1 = new Person(39);
const p2 = new Person();
console.log(p1);
console.log(p2);

Js 에서 private 변수를 표현하기 위해서 _ 를 붙혔었는데 TS 에서 관례적으로 private 변수 앞에는 _를 붙힌다. ( 없어도 상관 없음. )

initialization in constructor param

생성자 안에 접근 제어자를 넣음으로서 생성함과 동시에 할당가지 할수 있다.

1
2
3
4
5
6
class Person {
public constructor(public name: string, private age: number) {}
}

const p1 = new Person("koo", 39);
console.log(p1);

위코드는 아래 코드와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
class Person {
public name: string;
private age: number;

public constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}

const p1 = new Person("koo", 39);
console.log(p1);

Getter & Setters

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person {
public constructor(private _name: string, public age: number) {}

get name() {
return this._name + "Jayoun";
}

set name(n: string) {
this._name = n;
}
}

const p1 = new Person("koo", 39);
console.log(p1.name); // get 을하는 함수를 getter
p1.name = "Woongjae"; // set 을하는 함수를 setter
console.log(p1.name); // get 을하는 함수를 getter

getter 만 만들고 setter는 만들지 않는 방식으로 읽기만 가능한 프로퍼티를 만들 수 있다.

readonly properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person {
public readonly name: string = "Mark";
private readonly country: string = "Korea";

public constructor(private _name: string, public age: number) {
this.country = "korea";
}

hello() {
// this.country = 'China';
}
}

const p1 = new Person("koo", 39);
console.log(p1.name); // get 을하는 함수를 getter
// p1.name = "Woongjae"; // readonly 이기 대문에 할당할 수 없다.
console.log(p1.name); // get 을하는 함수를 getter

readonly 를 사용하여 get 만 할 수 있는 프로퍼티를 만들 수 있다. 이때 할당은 처음 프로퍼티를 생성하는 부분과 생성자에서만 할 수 있다.

Index signatures in Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// class => object
// {mark: 'male', jade: 'male} Class A
// {chole: 'female', alex: 'male', anna: 'female'} Class B

// 동적이라면?
class Students {
// [index: string]: string; // 어떤 문자열이 와도 쓸 수 있다.
[index: string]: "male" | "female"; // 받을 수 있는 것 정의 할 수 있음.

mark: "male" = "male";
}

const a = new Students();

a.mark = "male";
a.jade = "male";

console.log(a);

const b = new Students();

b.chole = "female";
b.alex = "male";
b.anna = "female";

console.log(b);

동적으로 프로퍼티가 생기는 형식일때 사용할 수 있다. 받을 수 있는 프로퍼티를 정의해 주는 것도 가능하다.

static properties & method

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Person {
public static CITY = "Seoul";
private static privateCity = "bu";
public static hello() {
console.log("안녕하세요.", Person.privateCity);
}

public normalHello() {
console.log("안녕", Person.CITY);
}

public change() {
Person.CITY = "LA";
}
}

Person.hello();
console.log(Person.CITY);

const p1 = new Person();
// p1.hello(); // 이렇게 사용할 수 없음.
p1.normalHello();

const p2 = new Person();
p2.normalHello();
p1.change();
p2.normalHello();

static 키워드를 붙히면 인스턴스를 생성하지 않아도 클래스 이름으로 접근해서 사용할 수 있다.

Singltons 패턴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class ClassName {
private static instance: ClassName | null = null;

// 매개체로 이용해서 객체를 꺼내옴.
public static getInstance(): ClassName {
// ClassName 으로 부터 만든 Object가 있으면 그걸 리턴
// 없으면, 만들어서 리턴
if (ClassName.instance === null) {
ClassName.instance = new ClassName();
}

return ClassName.instance;
}

// new 를 직접 호출 할 수 없게 함.
// 다른 오브젝트 생성 금지
private constructor() {}
}

// 만들어진 단일 오브젝트를 공유하는 개념
const a = ClassName.getInstance();
const b = ClassName.getInstance();

console.log(a === b);

생성자 함수를 private 접근 제어자를 사용해서 밖에서 호출하지 못하도록함. getInstance() 같은 함수를 사용해서 인스턴스가 있다면 반환하고 인스턴스가 없다면 새롭게 생성한후 할당, 프로퍼티로 있는 인스턴스를 넘겨준다.

이렇게 함으로서 클래스로쿠터 단 하나의 오브젝트만 생성해서 사용하는 패턴을 만들 수 있다.

클래스의 상속

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Parent {
constructor(protected _name: string, private _age: number) {}

public print(): void {
console.log(`이름은 ${this._name} 이고 나이는 ${this._age} 입니다.`);
}

protected printName(): void {
// 프로텍티트 접근 제어자를 통해서 부모 클래스의 private 프로퍼티도 접근 가능.
console.log(this._name, this._age);
}
}

class Child extends Parent {
// 접근 제어자도 오버라이드 됨.
// public _name = "Mark Jr.";

public gender: string = "male";

constructor(age: number) {
super("Mark Jr.", age);
this.printName();
}
}

// const p = new Parent("Mark", 39);
// p.print();

// const c = new Child("son", 39);
const c = new Child(5);
c.print();

부모 클래스에서 protected 로 선언한 함수 또는 프로퍼티는 클래스 외에서 접근은 불가능하지만, 상속받은 자식 클래스에서 접근은 가능하다. 자식 클래스에서 기본 생성자가 아니라 따로 만들었다면, super()를 사용하여 부모 클래스이 생성자를 호출해 주어야 한다. 그렇게 해야지 부모 클래스의 값이 할당 되고 this키워드를 사용하여 호출, 사용할 수 있게 된다.

abstract

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// new 불가 , 상속후 완전하게 만든다음에 사용 가능.
abstract class AbstractPerson {
protected _name: string = "Mark";

// 구현 하지 않음 // 클래스의 abstract 붙여야 함.
abstract setName(name: string): void;
}

class Person extends AbstractPerson {
setName(name: string): void {
this._name = name;
}
}

const p = new Person();
p.setName("Koo");

완전하지 않은 클래스로서 외형만 만든다고 생각하면된다. 완전하지 않기 때문에 new로 생성할 수 없다. 추상 클래스를 상속 받는 자식 클래스에서는 abstract로 작성한 완전하지 않은 메서드를 완전히 구현해야 new로 생성할 수 있다.