TypeScript
20210511 TypeSciprt 06 class : contructor(optional) & initialize(async), 접근제어자(private, protected), getter&setter, readonly, IndexSignature, static, singleton(싱글톤 패턴), 상속(extends, super), abstract
TypeScript06
Class
클래스?
- ojbect를 만드는 blueprint (설계도)
- 클래스 이전에 object를 만드는 기본적인 방법은 function
- JS는 es6 부터 class를 사용 가능 (접근 제한 키워드가 부족함)
- OPP을 위한 초석
- TypeScript 에서는 클래스도 사용자가 만드는 타입의 하나임 (JS보다 강력한 class 기능)
class 선언과 사용
class 선언&사용 : 기본 방법
- class 키워드 사용
- class 이름은 대문자
- new 키워드를 이용하여 class를 통해 object를 생성할 수 있음
- 기본 property에 값을 할당하여 class를 만들고 object를 생성하면 기본 property가 초기값으로 설정되어 object가 만들어짐
class Per {
name: string = "kim";
}
const per = new Per();
class 선언&사용 : 생성자를 이용한 방법
- constructor 키워드를 사용해서 나중에 property에 값을 무조건 넣게 지정할수 있다. (일단은, class 안에서 값 자체가 할당 안되어 있어도 compile error가 없음)
- this 키워드를 사용해서 class에 있는 property를 class 내부에서 사용할 수 있음
- object 생성시 class 안에 constructor를 이용하여 object를 생성하면서 값을 전달할 수 있다.
class Person0 {
name; // property
constructor(name: string) {
this.name = name;
}
}
contructor & initialize
- strict 모드 옵션이 켜져있고 constructor가 없는 class의 property는 반드시 초기화를 시켜주어야 함으로 class 내부적으로 값을 할당 해주어야 한다. (초기화를 검사하는 옵션 :
strictPropertyInitialization
) - 만약, 내부적으로 값 할당 안하고 나중에 외부에서 값을 할당해 준다고 하더라도 compile error는 발생함 (class는 불완전하게 느끼고 runtime까지 고려 못함)
- 예) age는 number이지만 할당 되지 않으면 나중에 값을 불러올때 undefined 가 됨으로
- 만약 초기값을 할당 안하고 나중에 개발자가 신경써서 한다는 것을
!
을 사용해서 표시하고 compile error를 일단은 벗어날수 있다.
class Person1 {
name: string = 'Jerry';
// age: number; // error 발생
age!: number; // ! 사용할땐 주의하자 나중에 값할당 안하면 온전히 개발자 잘못이므로
}
const p1: Person1 = new Person1();
console.log(p1);
console.log(p1.age); // undefined 뜨게 됨 (type은 number라고 뜨지만)
p1.age = 2; // runtime 보완 해도 고려 안됨 (!를 사용 안하면)
console.log(p1.age); // ok (위에서 2로 할당 시켜 줬기 때문에)
초기화 할때 값을 할당하거나 안하고 싶은 경우 (type 안정성 유지)
- constructor 에서 매개변수에
?
을 붙여 optional로 만든다. - 하지만, type은 안정화 시켜줘야 하기 때문에 contructor 안에 if 문을 통해서 undefined 시 해당 type의 기본 값을 할당하게 하여 어쨌거나 해당 type을 만족하도록 한다.
class Person2 {
name: string = "Tom";
age: number;
constructor(age?: number) {
if (age === undefined) {
this.age = 1;
} else {
this.age = age;
}
}
}
const p2: Person2 = new Person2(); // OK
const p2_1: Person2 = new Person2(24); // OK
비동기적으로 초기화 하고 싶은 경우
- contructor 키워드에는 async를 붙일 수 없음
- 초기화 할수 있는 메서드를 만들어서 활용해야 함 (async 가능)
- class의 메서드를 통해서 값을 할당을 통해 초기화 한다고 해도 class는 해당 메서드를 고려하지 못함으로 compile error가 발생함 그래서 여기에서도 property에
!
을 사용함
class Person3 {
name: string = 'asyn_test';
// age: number; // error
age!: number;
async init() {
this.age = 2
}
}
const p3: Person3 = new Person3();
await p3.init();
정리
- 생성자 함수가 없으면, 디폴트 생성자가 불린다.
- 프로그래머가 만든 생성자가 하나라도 있으면, 디폴트 생성자는 사라진다. (즉, 우선적으로 contructor가 씌워진다.)
- strict 모드에서는 프로퍼티를 선언하는 곳 또는 생성자에서 값을 할당해야 함
- 프로퍼티 선언하는 곳 또는 생성자에서 값을 할당하지 않는 경우에는 !를 붙여 위험 표현
- 클래스의 프로퍼티가 정의 되어 있지만 값을 대입하지 않으면 undefined임
- 생성자에는 async를 설정할 수 없다.
접근 제어자 (access modifier : public, private, protected)
- 접근 제어자는 property, method, contructor 모두 붙일 수 있음
- TS는 아무 설정 없으면 class를 외부에서 모두 접근 가능함 (설정 안하면 default로 public인것임)
public
을 명시해도 상관 없음private
로 해놓으면 밖에서 호출이 불가해짐 (private 키워드가 나오기 전에 암묵적으로_
를 붙여서 밖에서는 사용하지 마세요라고 모양을 통일해주었음)- contructor에 private 설정은 new 키워드를 통한 object를 만들수 없게 함
- 그래서 private해 놓으면 언더바
_
를 붙여주는 습관이 남아 있음
class Person4 {
public name: string = '접근제어자'; // 내부 외부 접근
private _age!: number; // 내부에서만 접근 '_' 붙임
public constructor(age?: number) {
if (age === undefined) {
this._age = 1;
} else {
this._age = age;
}
}
public async init() {}
}
const p4: Person4 = new Person4(444);
console.log(p4); // Person4 { name: 접근제어자, _age: 444} 로 다보이기는 함
console.log(p4._age) // error 접근 불가!!!!! private
p4.init(); // 가능
접근제어자로 contructor에서 바로 property까지 작성 시키는 방법 (꿀팁)
- this 를 사용하고 property를 선언해 줄 필요 없이 constructor 파라미터에 접근제어자를 붙여 전체적으로 코드를 줄여서 사용이 가능함 (property 명과 constructor의 parameter 명이 같게 쓰는 경우)
- 즉, Person5와 Person5_1는 같은 기능을 함 (private 설정도 가능함, object를 찍으면 볼순 있지만 접근해서 값을 바꾸거나 그것만 가져올 수는 없음)
// property 명과 constructor의 parameter 명이 같게 됨
class Person5 {
public constructor(public name: string, private _age: number) {}
}
class Person5_1 {
name: string;
private _age: number;
constructor(name: string, age: number) {
this.name = name;
this._age = age;
}
}
const p5: Person5 = new Person5('접근 제어자를 활용한 방법', 5);
console.log(p5); // Person5 { name: '접근 제어자를 활용한 방법', _age: 5 }
getter & setter
- 값을 가져오고 새로 세팅하기 직전에 무언가를 할수 있고, 해당 값의 가공도 가능함
get
,set
키워드 사용- 내부적으로 활용하는 private property도 접근하게 할수 있음
- getter와 setter의 짝으로 가지고 있어야 좋음
- get이 없고 set만 있으면 compile 에러는 없지만 runtime으로 돌려보면 가져오는 경우는 없는 프로퍼티로 생각할 수 있어 undefind로 뜨게 됨
- set이 없고 get만 있으면 해당 프로퍼티에 값을 할당하는 코드에 compile 에러를 내면서 알려줌 (이 데이터는 읽기만 가능하다라는 상황 연출 가능)
class Person6 {
public constructor(private _name: string, public age: number) {}
get name() {
// 직전에 할수 있는 무언가
// console.log('get');
return this._name;
}
set name(n: string) {
// 직전에 할수 있는 무언가
// console.log('set');
this._name = n;
}
}
const p6: Person6 = new Person6('raccoon', 6);
console.log(p6.name); // get을 하는 함수 : getter (사용하면 get, set하기 직전에 무언가를 할 수 있음 그리고 데이터 가공도 됨)
p6.name = 'changed!'; // set을 하는 함수 : setter
console.log(p6.name); // 다시 get raccoon -> changed!로 표현된것 확인
readonly property
readonly
키워드가 들어간 property- readonly 사용하면 해당 property는 값을 가져오는 것만 가능하고, 값 설정이 불가하다. (초기화시 값 할당만 가능)
- contructor 아니면 기본 property 선언시 할당 가능
class Person7 {
public readonly name: string = 'Tom';
private readonly country: string; // 초기값도 가능함
public constructor(private _name: string, private age: number) {
this.country = 'korea'; // readonly 여도 constructor 안에서만 값 변경이 가능함 즉, readonly면 초기값을 지정하는 곳에서만 값을 할당하고 변경 가능하지 그외에서는 불가함
}
hello() {
// this.country = "China" // 일단 private라서 안에서는 사용 가능하지만 readonly라서 값을 바꾸는 행위는 불가하고 가져는 것만 가능
}
}
const p7: Person7 = new Person7('Tom', 7);
console.log(p7.name);
p7.name = "raccoon!" // error : 값을 바꾸는 행위를 했기 때문에 compile 에러 발생
Index Signature
- 값이 동적으로 들어오는 경우에 사용 (딱히 값이 몇개가 들어올지 모를때, type만 만족시키는 property를 몇개든지 만들수 있음)
- optional property 느낌이라서 최초값을 적어줄 필요는 없다. 있을 수도 있고 없을 수도 있기 때문에
- index string으로 해놓으면 그냥 해당 조건의 모든 property에 type을 지정하게 되어 영향을 주게 됨
- 추론할 수 없는 type이 지정되어 있으면 class 내부에서 property에 값을 넣어준다고 type이 추론되어 지정되진 않음 -> type을 명시해줘야 함
class Students {
[index: string]: 'male' | 'female';
mark: 'male' = 'male'; // mark에 값을 지정한다고 해서 male이라는 type이 되는 것은 아니기 때문에 error가 뜨기 때문에 명시적으로 type을 적어주어야 함
// mark 라는 property는 항상 male이라는 type에 male값을 가지고 있다
}
const a = new Students();
a.mark = 'male';
a.jade = 'male';
console.log(a); // Students { mark: 'male', jade: 'male' }
const b = new Students();
b.chloe = 'female';
b.alex = 'male';
b.anna = 'female';
console.log(b); // Students { chloe: 'famale', alex: 'male', anna: 'famale' }
Static property & methods
- static을 사용하면 object 상관없이 공통적으로 사용하고 싶은 메서드나, property를 사용 할 수 있게 됨
- static으로 지정된 method 그리고 property는 object를 생성해서 사용하는게 아니라 선언된 class 명에서 시작하여 사용함
class명.static property명 or method명
- private 에서 static 사용된 property라도 class 내부에서 자유롭게 사용이 가능함
- static property의 값을 변경하는 코드를 일반 메서드에 담으면 모든 object에서 static 값을 변경시킬 수 있고 해당 값은 모든 object에서 볼때도 다 반영되어 있다.
- static property를 일반 메서드에 담아 return 등을 시키면 모든 object에서 해당 값에 접근이 가능하다.
- 즉, 다른 object에서 static property를 변경하면 다른 object에도 반영이 된다는 것 -> 공유 된다.
- static method에서는 this를 통한 변수 사용은 못함 하지만, 일반 method에서는 static property 사용 가능
class Apple {
public static CITY = 'seoul';
private static COUNTRY = 'Korea';
private name = 'kim';
hi() {
console.log(this.name);
}
static thanks() {
console.log('감사합니다!')
}
public hello() {
console.log('안녕하세요!', Apple.COUNTRY);
// static property class 내부에서 자유롭게 사용가능
}
public change() {
Apple.COUNTRY = 'USA';
}
}
const p10 = new Apple();
// p10.thanks(); // error : static이 붙여지면 해당 함수를 object에서는 메서드로 생각 안함
Apple.thanks(); // static method 클래스명에서 바로 호출하여 사용이 가능함
Apple.CITY; // static property
p10.hi(); // static method에서는 this를 통한 변수 사용은 못함 하지만
//일반 method에서는 static property 사용 가능
const p11 = new Apple();
p11.hello(); // 안녕하세요! Korea
p10.change();
p11.hello(); // 안녕하세요! USA
싱글톤 패턴 (Singleton pattern)
- 단일객체 패턴
- 어플리케이션이 실핻되는 중간에 class로부터 다른 하나의 object만 생성을 해서 사용하는 패턴
- 단일 오브젝트를 공유하는 패턴
- 생성자를 통해 생성되는 object를 하나만 만들어서 다른 변수에서는 전에 만들어둔 object를 불러와 재사용하는 것
- new라는 키워드를 통해서 object를 생성하는 것을 막아야함 -> new를 직접 호출 할수 없는 상황이 됨 -> 다른 매개체를 만들어서 사용 할수 있게함
class ClassName {
private static instance: ClassName | null = null; // 매개체로 instance static property
// 초기에는 object가 존재하지 않으므로 null union으로 만듦
public static getInstance(): ClassName {
// new 생성자 대신 해당 object를 만들거나 가져오는 기능의 getInstance static 메서드
if (ClassName.instance === null) {
// ClassName으로 부터 만든 Object가 없으면, 만들어서 return 하는 로직 만들기
ClassName.instance = new ClassName();
}
// ClassName으로 부터 만든 Object가 있으면 그것을 return
return ClassName.instance;
}
private constructor() {}
}
const s1 = ClassName.getInstance();
const s2 = ClassName.getInstance();
console.log(s1 === s2); // true
상속 (inheritance)
extends
키워드를 활용해서 자식은 상속을 받음protected
접근제어자 키워드 : 외부에서는 사용 불가하지만 상속받은 자식은 접근 가능- 상속 받은 자식을 통해서 property, method, contructor 사용시 자식이 아무것도 없으면 부모를 찾아서 해당 기능을 받아 사용함
- overriding : 같은 이름의 property, method 또는 contructor가 겹치는 경우 자식이 우선되어 덮어씌워져서 기능을 함, 심지어 접근제어자도 overriding이 됨
super
키워드는 부모의 contructor에 접근하여 세팅하거나 가져올 수 있음- 부모의 값을 쓰던 안쓰던 자식에서 contructor를 사용하면 항상 super을 안에 써서 부모 contructor에 값을 할당 시켜서 type 안정성을 지켜줘야 함
class Parent {
constructor(protected _name: string, private _age: number) {}
public print(): void {
console.log(`이름은 ${this._name} 이고, 나이는 ${this._age} 입니다.`);
}
protected printName(): void {
console.log(this._name, this._age);
}
}
const super1 = new Parent('Mom', 30);
super1.print(); // 이름은 Tom 이고, 나이는 30 입니다.
class Child extends Parent {
public _name = 'son'; // 접근 제어자 까지도 overriding이 됨
public gender = 'male'; // 부모에 없는 property 생성
constructor(age: number) {
// 자식 자신만의 생성자를 만드는 경우 부모의 생성자도 setting을 해줘야함 공유하고 있어서 그래서 super 키워드를 사용함
super('son', age);
this.printName(); // 부모 메서드 사용
}
}
const sub1 = new Child(7); // son 7
sub1.print(); // 이름은 Son 이고, 나이는 7 입니다.
sub1._name; // son
- 자식의 constructor 안에서 this. 무언가를 호출하려는 경우 부모의 property가 setting 되어 있지 않다면 super 키워드를 통해서 먼저 세팅 해줘야함 (순서 주의)
- 부모의 protected 함수를 통해서 자식이 그것을 받아 사용하고 부모의 protected에 부모 자신만의 private property를 사용하게 되면
- 자식은 부모의 private property 까지 접근 가능하게 됨,
- 그래서 서로 너무 얽히지 않도록 주의 해야함, 오염되지 않도록
abstract 키워드
- 완전하지 않은 객체를 만들 수 있고, 상속과 같은 것으로 보완하게 안내하여 완성시켜 사용할 수 있다.
- abstract 키워드를 사용하려면 abastract class여야 해서 class 키워드 앞에 abstract를 붙임
- 함수 구현부 바디 없이 부모 작성 가능 -> 자식이 사용할 땐 이를 보충해서 사용할 수 있음
abstract class AbstractPerson {
protected _name: string = 'Tom';
abstract setName(name: string): void; // 함수지만 abstract 키워드를 붙임으로서 함수 구현부(바디)를 작성하지 않음
}
// 기능이 완전하지 않아서 new 키워드로 object를 생성할 수 없음
class DetailPerson extends AbstractPerson {
setName(name: string): void {
this._name = name;
}
}
const ab1 = new DetailPerson();
ab1.setName('Jerry');