Class
basic concepts
js에서 class는 같은 구조의 객체를 빠르게 만들 수 있도록 도와준다(a blue print of objects). ts를 이용하면, class에 타입을 더해서 사용할 수 있다.
class Department {
// property 타입 정의
name: string;
// 새로운 객체를 생성할 때 name 매개변수에 인자를 전달할 수 있다
constructor(name: string) {
this.name = name;
}
// methods
describe() {
// console.log('Department: ' + name);
console.log('Department: ' + this.name);
}
}
const hr = new Department('hr'); // hr의 타입은 Department이다
hr.describe(); // => Department: hr
const frontEnd = {
describe: hr.describe,
}
frontEnd.describe(); // 컴파일 에러 ❌ => Department: undefined
- console.log(name)
- 메서드(describe) 내에서 name 변수를 찾는다.
- 메서드 내부에 없다면, 클래스 외부(전역)에서 name 변수를 찾는다.
- window 객체의 프로퍼티 중 하나인 name을 찾는다.
- console.log(this.name)
- 클래스 내부의 this는, 클래스에 의해 생성될 객체 자체를 가리킨다.
- 따라서 this.name은 객체의 name을 찾는다.
- frontEnd.describe()
- 함수를 호출할 때, 함수 내부의 this는 이 함수를 호출하고 있는 것을 가리킨다.
- ex) frontEnd.describe()에서 describe의 this는 frontEnd을 가리킨다.
class Department {
// ...
describe(this: Department) {
console.log('department : ' + this.name)
}
}
const backEnd = {
age: 12,
describe: frontEnd.describe,
}
backEnd.describe(); // 컴파일 에러⭕
/*
The 'this' context of type '{ age: number; describe: (this: Department) => void; }'
is not assignable to method's 'this' of type 'Department'.
*/
- describe(this: Department){ }
- describe를 실행할 때, 이 함수 내에서 호출하고 있는 this.name의 this는 Department 클래스를 기반으로 한 instance 객체여야 한다는 것을 의미한다.
- backEnd 객체 자체에 name 프로퍼티를 추가하거나, new Department를 이용해서 backEnd 객체를 생성하면 컴파일 에러를 해결할 수 있다.
// 1.
class Department {
employees: string[] = [];
addEmployee(name: string) {
// validation check...
this.employees.push(name)
}
describeEmployess() {
console.log(this.employees)
}
}
const frontEnd = new Department();
frontEnd.addEmployee('Harry'); // 가능
frontEnd.employees.push('Harry'); // 가능
// 2.
class Department {
private employees: string[] = [];
//...
}
frontEnd.addEmployee('Harry'); // 가능
frontEnd.employees.push('Harry'); // 불가능
// 3. 약식형
class Department {
private employees: string[] = [];
constructor(private readonly id: string, public name: string) {
// this.id = id 작업 생략 가능
// public 키워드 생략 불가능
}
// ...
}
- employee를 추가하는 방법이 두 가지가 있는데, 이는 문제가 될 수 있다.
- addEmployee에서 유효성 검사를 진행할 수 있다.
- 그런데 employees.push를 이용하면 유효성 검사 없이도 값을 추가할 수 있게 된다.
- 뿐만 아니라 employee를 추가하는 방법이 통일되지 않아서, 팀 프로젝트가 중구난방 식이 될 수 있다.
- private 키워드를 이용한다.
- promerty나 method 앞에 priavte 키워드를 작성할 수 있다.
- private 키워드가 사용된 프로퍼티, 메서드는 class를 이용해 생성되는 객체 인스턴스 내부에서만 접근할 수 있다. => 즉, frontEnd 객체의 addEmployees 메서드를 통해서만 employees 속성에 접근할 수 있다.
- private 키워드를 사용하지 않은 경우, 자동으로 public이 된다(기본값). => 외부에서도 접근할 수 있다.
- readonly
- 값이 초기화 된 이후에 변경되어서는 안되는 프로퍼티의 경우, readonly 키워드를 사용하면 된다.
more concepts
inheritance
class Department {
private employees: string[];
constructor(public name: string, private id: string) {
}
describe() {
console.log('Department: ' + this.name);
}
addEmployee(name: string) {
this.employess.push(name);
}
}
// 1.
class ItDepartment extends Department {
}
new ItDepartment('backend', '12345'); // class가 텅 비어있어도 에러가 발생하지 않는다
// 2.
class ItDepartment extends Department {
constructor(id: string, public reports: []string,) {
super(id, 'IT');
this.reports = reports;
}
addEmployee(name: string) {
if(name === 'Harry') return ;
this.employess.push(name); // error!
}
}
const it = new ItDepartment('12345', ['something...']);
it.describe(); // Department: it
// 3.
class Department {
protected employees: string[]; // private => protected
// ...
}
class ItDepartment extends Department {
// override
addEmployee(name: string) {
if(name === 'Harry') return ;
this.employess.push(name); // no error!
}
}
// 4. abstract
abstract class Department {
// ...
abstract describe(this: Department) : void;
// abstract describe?(this: Department) : void; => optional method
}
class ItDepartment extends Department {
// ...
describe() {
console.log(this.name);
}
}
- ItDepartment는 Department를 상속한다.
- 이 때 ItDepartment는 Department의 모든 것(프로퍼티, 메서드, constructor)을 자동으로 가져온다.
- => 클래스가 비어있어도 에러가 발생하지 않는다.
- 두번째 예제의 경우처럼 constructor를 이용하면, Department에서 정의된 속성 이외에도, 객체를 생성할 때 인자로 전달받을 값을 추가할 수도 있다.
- super() | Department의 constuctor를 호출한다.
- super를 호출한 다음, ths 키워드를 이용해서 새로운 프로퍼티를 추가할 수 있다.
- Department에 이미 정의되어 있는 메서드를 덮어쓰는 것도 가능하다.
- ItDepartment(상속받는 클래스)의 메서드에서 Department(상속하는 클래스)의 private 필드에 접근하려고 하면, 에러가 발생한다.
- private 필드는 그 필드가 정의된 클래스 내부에서만 접근 가능해서, 상속받는 클래스에서는 접근이 불가능하기 때문이다.
- protected | private 대신 protected를 사용하면, (여전히 외부에서 그 값에 접근하는 것은 불가능하지만) 상속받는 클래스에서도 그 프로퍼티에 접근할 수 있다.
- abstract | 인스턴스를 직접 만들지 않고 상속하기만 하는 클래스를 정의할 때, 또는 어떤 메서드가 모든 상속받는 클래스에 기본적으로 존재하되, 상속받을 때 이를 구체화 하고 싶다면 abstract 키워드를 사용할 수 있다.
- 클래스 이름 앞에 abstract 키워드(modifier)를 사용해야 한다.
- 메서드의 구체적인 내용은 상속받는 클래스에서 정의하고, 상속하는 클래스에서는 메서드의 타입만을 정의한다. => 구체적인 내용이 정해지는 상속받는 클래스 내부에서 이 메서드를 호출한다.
- 상속받는 클래스에서 메서드를 정의하지 않으면 에러가 발생한다.
- 상속하는 클래스에서 abstract 메서드의 modifier로 private을 사용할 수 없다(private abstract method is not supported).
getter & setter
private 필드는, 클래스 내부의 메서드를 통해서만 그 값에 접근할 수 있다. 이 때 getter 메서드를 이용하면 private 필드의 값을 확인할 수 있다.
class Department {
private employees: string[];
get lastEmployee() {
if(this.employess[this.employees.length - 1]) {
return this.employess[this.employees.length - 1];
} throw new Error('no employees');
}
set lastEmployee(value: string) {
if(!value) return;
this.addEmployee(value);
}
constructor(public name: string, private id: string) {
}
addEmployee(name: string) {
this.employess.push(name);
}
}
const it = new Department('it', '12345');
it.lastEmployee; // get => it.lastEmployee() ❌
it.lastEmployee = 'harry'; //set => it.lastEmployee('harry') ❌
- getter
- 반드시 무언가를 return 해야한다.
- 단순히 값을 확인하는 것 이외의 로직을 추가할 수도 있다.
- getter 메서드는 함수처럼 실행하는 것이 아니라, 프로퍼티처럼 접근해야 한다.
- setter
- set 함수 내에서 원하는 로직을 작성할 수 있다.
- setter 메서드 또한 함수처럼 실행하는 것이 아니라, 프로퍼티 처럼 접근해서 값을 할당해야 한다.
static properties & methods
Math.round() 는 Math 클래스 자체의 round 메서드를 호출하는 것이다. 즉 Math의 인스턴스를 생성하지 않고(ex. const myMath = new Math(); myMath.round(); ), 클래스 자체에 접근한다. 이러한 속성/메서드를 static property/method 라고 부른다.
class Department {
// ...
static addEmployee(name: string) {
return { name }
}
static fiscalYear = 2022;
}
const employee1 = Department.addEmployee('Harry');
const fiscal = Department.fiscalYear;
- static 키워드를 사용해서 프로퍼티/메서드를 정적으로 만들 수 있다.
- this.fiscalYear❌ => Department.fiscalYear⭕
- 클래스 내부에서 this는 new 키워드에 의해 생성될 인스턴스 객체를 가리킨다.
- fiscalYear와 같은 정적 속성은 인스턴스 객체에 존재하는 것이 아니라, 클래스 자체에 존재하는 것으로 this를 이용해 접근하는 것이 불가능하다.
- 클래스 내부에서 정적 속성/메서드에 접근하고자 할 때에는 그 클래스의 이름(Department)을 이용한다.
singleton
객체지향 프로그래밍에서 singleton이란, 특정 클래스의 인스턴스를 정확히 하나로 제한하는 것을 말한다. 예를 들어 하나의 회사에 여러 개의 It 부서를 만들 필요가 없다면, new ItDepartment 를 이용해서 만들 수 있는 인스턴스는 하나로 제한하는 것이 좋다. 즉 ItDepartment 클래스의 인스턴스는 정확히 하나로 제한하는 것이 좋다.
class ItDepartment extends Department {
private static instance: ItDepartment;
private constructor(id: string, public reports: string[],) {
super(id, 'IT');
this.reports = reports;
}
static getInstance() {
// 이 때의 this는 ItDepartment 클래스 자체를 가리킨다
if(this.instance) return this.instance;
// constructor(new ItDepartment)가 private이므로, 클래스 내부 메서드에서 호출 가능
this.instance = new ItDepartment('12345', ['report1']);
return this.instance;
}
// ...
}
// const it = new ItDepartment('123', ['asdf']); 에러!
const it = ItDepartment.getInstance(); // 에러가 발생하지 않는다
const it2 = ItDepartment.getInstance(); // it와 it2는 같다
- private constructor
- constructor 함수는 외부에서 호출할 수 없다. 즉, 클래스 외부에서 new ItDepeartment 생성자 함수를 호출하는 것이 불가능하다. => 대신 getInstance라는 메서드를 정의하고, 그 내부에서 instance를 생성한다.
- static getInstance
- 클래스의 인스턴스가 이미 존재하는지 확인하고, 없을 때에만 새로운 인스턴스를 반환한다.
- static 메서드이므로 클래스 자체를 통해서만 호출할 수 있다. => ItDepartment.getInstance()
- static 메서드에서 this는 ItDepartment 자체가 된다(ItDepartment를 통해서만 호출할 수 있기 때문).
- private static instance: ItDepartment
- instance라는 프로퍼티를 만든다. => 여기에 ItDepartment 타입을 부여한다.
- static 프로퍼티이므로, 클래스 자체(ItDepartment)에서만 접근할 수 있다. => ItDepartment.instance
- 동시에 private 프로퍼티이므로, 클래스 내부에서만(이 클래스의 메서드, 속성을 통해서만) 이 값에 접근(조작? 무언가를 하는 것?)할 수 있다.
'Study > TypeScript' 카테고리의 다른 글
Generic (0) | 2022.07.24 |
---|---|
Advanced Types (0) | 2022.07.23 |
Interface (0) | 2022.07.21 |
tsconfig | ts compiler (0) | 2022.07.17 |
Types (0) | 2022.07.17 |