오늘 한 일
오늘까지 typescript를 끝내고 nestJS를 들으려 했으나 생각보다 typescript의 분량이 많았다.
typescipt에서 오늘 배운 부분을 크게 나누어보면 클래스와 인터페이스, 제네릭에 대해서 배웠다.
내일은 데코레이터부분을 보고 바로 개인 프로젝트를 만들어봐야 할 것 같다.
배운 부분
게터와 세터
게터 : 값을 가져오기 위해 함수나 메서드를 실행하는 프로퍼티로서, 게터를 사용하면 복잡한 로직을 구현할 수 있다.
게터를 정의할 때는 get 키워드를 사용한 다음 원하는 대로 이름을 지으면 되는데, 일반적으로 엑세스하려는 프로퍼티의 이름과 관련지어 이름을 짓는다.
게터 메서드이기 때문에 값을 반환해야 한다.
class AccountingDepartment extends Department {
private lastReport: string;
get mostRecentReport() {
if(this.lastReport) {
return this.lastReport
}
thorow new Error("No report found.")
}
이렇게 하면 private 프로퍼티인 lastReport에는 직접적인 점 표기법으로 접근할 수는 없지만
mostRecentReport 프로퍼티로 lastReport의 값은 가져올 수 있다.
게터 메서드라고는 하지만 메서드를 실행하는 () 것이 아니라 일반적인 프로퍼티를 액세스하듯이 사용한다
console.log(accounting.mostRecentReport)
세터 : 게터와 유사하게 set 키워드를 사용해 정의하고 원하는 이름을 붙이면 되는데 일반적으로 설정하려는 프로퍼티의 이름과 관련지어 짓는다.
게터와 동일하게 메서드처럼 정의하는데 세터는 사용자가 전달하는 값을 인수로 받아야한다.
세터도 게터와 마찬가지로 메서드를 실행하는 것이 아니라 일반적인 프로퍼티를 액세스 하듯이 사용한다.
accounting.mostRecentReport = '';
- 정적 메서드 : 클래스를 인스턴스화 하지 않고 사용할 수 있는 정적 메서드는 메서드 이름 앞에 static 키워드를 붙이면 된다.
class Department {
// private id: string;
// private name: string;
protected employees: string[] = [];
constructor(private readonly id: string, public name: string) {
// this.name = n;
// this.id = id;
}
static createEmployee(name: string) {
return { name: name };
}
describe() {
console.log(`Department (${this.id}): ${this.name}`);
}
addEmployee(employee: string) {
this.employees.push(employee);
}
printEmployeeInformation() {
console.log(this.employees.length);
console.log(this.employees);
}
}
다음과 같이 new 생성자로 인스턴스를 생성하지 않고 만들 수 있는 것을 확인할 수 있다.
const employee1 = Department.createEmployee('Max');
console.log(employee1);
정적 프로퍼티도 마찬가지의 방법으로 만들 수 있다.
class Department {
static fiscalYear = 2020;
// private id: string;
// private name: string;
protected employees: string[] = [];
constructor(private readonly id: string, public name: string) {
// this.name = n;
// this.id = id;
}
static createEmployee(name: string) {
return { name: name };
}
describe() {
console.log(`Department (${this.id}): ${this.name}`);
}
addEmployee(employee: string) {
this.employees.push(employee);
}
printEmployeeInformation() {
console.log(this.employees.length);
console.log(this.employees);
}
}
class ITDepartment extends Department {
constructor(id: string, public admins: string[]) {
super(id, 'IT');
this.admins = admins;
}
}
// 확인
console.log(Department.fiscalYear);
한 가지 중요한 점은 클래스에 정의된 정적 메서드와 프로퍼티를 정적이지 않은 요소에서는 사용할 수 없다.
예를 들어, 생성자에서 fiscalYear를 액세스해 출력하라고 한다면 이 코드는 작동하지 않는다.
constructor(private readonly id: string, public name: string) {
// this.name = n;
// this.id = id;
console.log(this.fiscalYear)
}
fiscalYear가 정적 프로퍼티라는 오류가 발생한다.
이처럼 생성자와 같이 정적으로 지정되지 않은 모든 메서드에서 (참고로 생성자는 정적으로 정의할 수 없다) 정적 프로퍼티를 액세스 할 수 없다.
여기서 this는 클래스를 기반으로 생성한 인스턴스를 의미하는데, 정적 프로퍼티와 메서드는 기본적으로 인스턴스와 별개로 사용되므로 인스턴스에서 액세스 할 수 없다.(this 키워드로 액세스 할 수 없다).
클래스 안에서 정적 프로퍼티 또는 메서드를 사용하려면 클래스 이름으로 액세스 해야한다.
constructor(private readonly id: string, public name: string) {
// this.name = n;
// this.id = id;
console.log(Department.fiscalYear) //클래스 이름으로 액세스
}
추상 클래스
코드를 작성하는 개발자가 특정 클래스를 상속받을 때 특정 메서드를 구현하거나 특정 메서드를 오버라이드하도록 강제하고 싶을 수 있다.
왜 강제할 필요가 있을까?
예를 들어 기본 클래스 Department를 상속받는 모든 클래스에서 특정메서드를 구현할 필요가 있고 상속받는 각 부서마다 메서드를 다르게 구현해야 할 때 이를 강제해야 한다.
공통으로 사용되는 메서드의 구현을 기본 클래스에 정의하지는 않지만 상속받는 각 클래스에서 각 메서드를 구현하도록 강제하는 것이다.
이렇게 하려면 기본 클래스에서 빈 메서드를 정의한 다음 상속받은 클래스에서 이 메서드를 오버라이드하도록 만들면 된다.
이 때, abstract 키워드를 사용한다. 이름 앞에 abstract가 달린 메서드를 하나 이상 정의하려면 클래스 이름 앞에도 abstract를 붙여야한다. 또한 빈 메서드를 정의해주어야 하기 때문에 본문이 구현되면 안된다.
abstract describe(this: Department): void; // 추상 클래스의 메서드
추가적으로 abstract로 지정된 추상 클래스는 인스턴스화 할 수 없다.
private 생성자
싱글톤 패턴에서는 한 클래스의 인스턴스를 정확히 1개만 생성한다. 정적 메서드나 프로퍼티를 사용할 수 없거나 사용하고 싶지 않을 때 유용하다. 하지만 클래스의 객체를 여러 개 생성하지 않고 정확히 1개만 생성할 수 있도록 제한해야 한다.
AccountingDepartment 클래스를 예로 들어 설명하겠다. new AccountingDepartment()를 여러 번 호출하는 것을 방지하려면 private 키워드를 사용해 AccountingDepartment의 생성자를 private으로 만들면 된다. 이렇게 하면 new 키워드를 사용해 인스턴스를 생성할 수 없다.
생성자가 private이기 때문에
const accounting = new AccountingDepartment('d2', []);
부분에서 오류가 발생한다. 클래스 안에서만 액세스 할 수 있기 때문이다. 그런데 클래스의 객체를 생성할 수 없다면, 어떻게 클래스 안에서 액세스 할 수 있을까?
그 방법은 클래스 자체에서 액세스 할 수 있는 정적 메서드를 사용하면 된다.
private static instance: AccountingDepartment;
// ...
static getInstance() {
}
새로 만든 프로퍼티인 instance는
정적 프로퍼티이므로 클래스 이름으로만 액세스 할 수 있고, private이므로 클래스 안에서만 액세스 할 수 있는 AccountingDepartment 프로퍼티이다.
인터페이스란
: 인터페이스는 객체의 구조를 정의한다.(객체가 어떻게 구성되어야 할지 정의한다)
interface키워드를 통해 생성할 수 있다. 이름은 class와 마찬가지로 대문자로 시작하는 것이 관례이며, 권장하는 사항이다.
interface Person {
name: string;
age: number;
greet(phrase:string): void;
}
여기에 Person 객체가 어떤 구조를 가져야 할지 정의하면 된다. 다만 클래스와는 다르게 블루프린트로 사용하지는 않는다. 커스텀 타입과 같이 사용한다고 생각하면 된다.
보이는 것과 같이 프로퍼티의 이름을 작성하고 저장할 값의 타입을 작성한다. 다만 고정된 값을 할당하지는 않는다.
프로퍼티와 마찬가지로 메서드를 정의할 때도 구조만 정의할 수 있다.
이렇게 정의해 둔 인터페이스를 어떻게 쓸 수 있을까?
왜 인터페이스를 정의할까?
예를 들어 객체의 타입을 확인할 수 있다.
let user1;
user1이라는 변수를 초기화하지 않고 선언해보자.
나중에 Person이라는 구조를 가진 객체를 저장하려고 한다. 이 때, Person이라는 타입을 할당해두면 된다.(인터페이스를 타입으로 사용하는 것이다)
let user1:Person;
이제 user1에 값을 할당할 때 문자열 name 프로퍼티와 age프로퍼티, 그리고 phrase 인수를 받아 아무것도 반환하지 않는 greet 메서드를 가지는 객체를 할당해야 한다.
주의할 점은 interface에서는 ; 세미콜론으로 프로퍼티와 메서드 정의를 구분했는데 실제로 값을 할당할 때는 객체를 생성할 때와 마찬가지로 쉼표로 만들어주어야한다.
user1 = {
name: "Max",
age:30,
greet(phrase:string) {
console.log(phrase + " " + this.name);
}
};
인터페이스는 그럼 왜 사용하는 것인가?
커스텀 타입을 만들어서 사용하면 되는 것 아닌가?
type Person = {
name: string;
age: number;
greet(phrase:string):void;
}
결론부터 말하자면 인터페이스와 커스텀 타입은 완전히 같지 않다.
물론 서로 바꿔 사용해도 문제없는 경우도 있지만 차이점이 있다.
중요한 차이점 중 하나는 객체의 구조를 정의하는데 인터페이스와 타입을 모두 사용할 수 있고 커스텀 타입을 사용하면 강좌 초반에 다루었던 유니언 타입과 같은 것들을 저장할 수 있어 타입이 더 유연해보이지만 인터페이스를 사용하면 훨씬 명확하다는 장점이 있다.
인터페이스로 정의하면 객체의 구조를 정의하고자 한다는 것을 명확하게 나타낼 수 있다.
따라서 객체의 타입을정의할 때는 커스텀 타입을 사용하기 보다는 인터페이스를 자주 사용한다.
또 다른 차이점은 클래스 안에 인터페이스를 구현할 수 있다는 것이다.
인터페이스를 자주 사용하는 이유는 클래스가 구현해야하는 구조를 인터페이스로 정의할 수 있기 때문이다.
class Person 뒤에 implement라는 키워드를 쓰고 클래스가 따를 인터페이스 이름을 적어준다. 이 때 상속과는 다르게 인터페이스는 여러 개의 인터페이스를 따를 수 있다. 인터페이스를 여러 개 따를 경우, 인터페이스끼리 콤마로 연결하면 된다.
이렇게 되면 클래스는 인터페이스의 구성을 따라야 한다. 원한다면 인터페이스에 적힌 프로퍼티나 메서드 이외에 더 많은 것을 넣어도 된다.
따라서 여러 클래스에서 기능을 공유하기 위해 인터페이스를 사용한다. 공통된 구현을 공유하지는 않지만 인터페이스를 사용해 클래스에 포함되어야 하는 기능의 구조를 정의할 수 있다.
추상 클래스를 사용했던 것과 유사하지만 인터페이스에는 아무런 구현도 하지 않았다. 추상 클래스에서는 오버라이드할 추상 메서드를 제공하면서도 완전히 구현된 부분도 함께 제공할 수 있었다.
interface Greetable {
name: string;
greet(phrase: string): void;
}
let user1: Greetable;
user1 = {
name: 'Max',
greet(phrase: string) {
console.log(phrase + '' + this.name);
},
};
인터페이스를 타입으로 받던 user1은 이렇게 사용했지만 (user1에 age프로퍼티를 가질 수 없다)
클래스로 인터페이스를 받게 될 경우는 아래와 같이 작성할 수 있다.(user1에 age 프로퍼티를 가질 수 있다)
interface Greetable {
name: string;
greet(phrase: string): void;
}
class Person implements Greetable {
name: string;
age = 30;
constructor(n: string) {
this.name = n;
}
greet() {
console.log(phrase + '' + this.name);
}
}
let user1: Greetable;
user1 = new Person('Max');
읽기 전용 인터페이스의 속성
인터페이스 안에 public이나 private 키워드는 사용할 수 없지만 readonly 접근 제한자는 사용할 수 있다.
interface 프로퍼티에 readonly를 설정하게되면 해당 프로퍼티는 한 번만 설정할 수 있도록 제한된다. 따라서 객체가 초기화된 후에는 프로퍼티를 수정할 수 없다.
interface 프로퍼티에 readonly를 지정해놨을 때 class에서 interface를 가져다 쓰면 readonly를 지정해놓은 프로퍼티에 따로 readonly 옵션을 넣을 필요 없이 한 번만 설정할 수 있도록 지정된다.
인터페이스의 확장
class와 마찬가지로 extends 를 써서 상속하는 형식으로 이루어진다.
interface Named {
readonly name: string;
}
interface Greetable extends Named{
greet(phrase: string): void;
}
만약 어떤 class가 Greetable이라는 인터페이스를 사용할 경우 name이라는 프로퍼티와 greet라는 메서드 두 개 모두 가져야 한다.
정의한 인터페이스가 더 있다면 쉼표로 구분해 추가하면 된다.
함수 타입으로서 인터페이스
tpye AddFn = (a:number, b:number) => number;
let add: AddFn;
add = (n1:number, n2:number)=> {
return;
}
이전에 배운것을 되짚어보면 type 키워드로 함수의 커스텀 타입을 지정할 수 있다.
하지만 커스텀 타입 대신 인터페이스를 사용할 수도 있다.
이전에 우리가 greet 메서드를 작성했던 것처럼 만드는 것인데, greet와는 다르게 익명함수로 만든 것이다
interface AddFn {
(a: number, b: number): number;
}
interface Greetable extends Named {
greet(phrase: string): void;
}
인터페이스에서의 선택적 매개변수, 속성
interface Named {
readonly name: string;
outputName?: string;
}
프로퍼티 이름 뒤에 물음표를 붙여 선택적 프로퍼티로 선언할 수 있다.
이렇게 선언하면 인터페이스를 구현하는 클래스에 이 프로퍼티가 있을 수도 있지만 없어도 된다.
class에서도 선택적 프로퍼티를 쓸 수 있는데 사용방법은 인터페이스와 동일하다.
인터페이스에서는 선택적 프로퍼티로 선언한 다음 클래스에서 구현할 때는 일반적인 프로퍼티로 만들어도 된다. 대신 이렇게 하면 항상 초기화되도록 로직을 만들어야 한다. this.name = n; 이런식으로!
아니면 인터페이스에서 선택적 프로퍼티로 선언하고 클래스에서도 선택적 프로퍼티로 설정하면 값을 할당하지 않아도 된다.
인터페이스는 인스턴스화 할 수 없으며, 컴파일되지도 않는다.
'TIL' 카테고리의 다른 글
2023_12_29 TIL (1) | 2023.12.30 |
---|---|
2023_12_28 TIL (1) | 2023.12.29 |
2023_12_26 TIL (2) | 2023.12.27 |
2023_12_22 TIL (1) | 2023.12.23 |
2023_12_21 TIL (1) | 2023.12.22 |