Jieunny의 블로그
Section 5. 클래스 & 인터페이스 본문
📣 강의 내용 정리
𝟭. 클래스란 무엇인가
✔️ 객체(클래스 내의 인스턴스) : 코드로 작업을 수행하면서 사용할 수 있는 구체적인 요소들
➰ 데이터를 저장하고 메서드를 실행하기 위해 메서드를 저장하는 데 사용하는 데이터 구조
✔️ 클래스 : 객체의 청사진
➰ 클래스를 사용하여 객체의 형태, 포함해야 하는 데이터, 메소드를 정의할 수 있다.
➰ 동일한 구조, 동일한 기능을 하는 여러 객체를 빠르게 복사할 수 있다.
class Department {
name: string;
constructor(n: string) { // 생성자 메서드
this.name = n;
}
}
const accounting = new Department('Accounting');
// Department { name: "Accounting" }
➰ 생성자 메서드(constructor) : 클래스와 연결되며, 만드는 모든 객체에도 연결되는 함수 -> 객체에 대한 초기화 작업이 가능하다.
➰ Accounting 값이 인수로서 생성자로 전달된다.
𝟮. 생성자 함수 및 'this' 키워드
✔️ 클래스 내부의 클래스 속성이나 메소드를 참조하려면 this 키워드를 사용해야 한다.
➰ this는 일반적으로 생성된 클래스의 구체적이 인스턴스를 참조한다.
class Department {
name: string;
constructor(n: string) { // 생성자 메서드
this.name = n;
}
discribe(this: Department) {
// discribe가 실행될 때 discribe 코드의 this는 Department 클래스에 기반한 인스턴스를 참조해야 한다는 뜻
console.log('Department' + this.name);
}
}
const accounting = new Department('Accounting');
// Department { name: "Accounting" }
accounting.discribe();
const accountingCopy = { name: 'DUMMY', describe: accounting.discribe };
accountingCopy.discribe();
// accountingCopy에 name을 전달하지 않으면 에러가 난다!
𝟯. 개인 및 '공용' 액세스 수정자
class Department {
name: string;
private employees: string[] = [];
constructor(n: string) { // 생성자 메서드
this.name = n;
}
discribe(this: Department) {
// discribe가 실행될 때 discribe 코드의 this는 Department 클래스에 기반한 인스턴스를 참조해야 한다는 뜻
console.log('Department' + this.name);
}
addEmployee(employee: string) {
this.employees.push(employee);
}
printEmployeeInformation() {
console.log(this.employees.length);
console.log(this.employees);
}
}
const accounting = new Department('Accounting'); // Department { name: "Accounting" }
accounting.addEmployee('Max');
accounting.addEmployee('Manu');
accounting.employees[2] = 'Anna';
// 이런 식으로 구현하면 안된다!!
// 클래스를 구현했으면 하나의 방법만 사용해라.
// employees를 private로 선언하면 해결가능 -> 이 코드는 에러난다.
accounting.discribe();
accounting.printEmployeeInformation();
✔️ private : 생성된 객체가 내부에서만 접근할 수 있는 속성으로 만드는 제어자
➰ 자바스크립트에는 최근까지 private, public 개념이 없었기 때문에 컴파일을 수행하는 버전에 따라 인식하지 못할 수도 있다.
➰ 타입스크립트만이 이 기능을 지원한다.
𝟰. 약식 초기화
// 원래 코드
class Department {
private id: string;
private name: string;
private employees: string[] = [];
constructor(n: string) {
}
}
// 약식 초기화 코드
class Department {
private employees: string[] = [];
constructor(private id: string, public name: string) {
// public을 따로 추가하는 이유
// 생성자에서 인수를 가져올 뿐만 아니라 이 클래스에 대해 정확히 동일한 이름으로 속성을 만들고 싶다는 뜻
// n을 name으로 바꾼 것 -> name 속성이 생성된 클래스에 생성될 수 있도록 한다.
}
}
✔️ 생성자 함수 안에 매개변수를 넣어줌으로써, 필드를 찾은 다음 값을 저장해야 하는 이중 초기화 코드를 한 번에 처리할 수 있다.
𝟱. 읽기 전용 속성
✔️ readonly : private나 public 이어서도 안되고 초기화 후에 변경되어서도 안되는 특정 필드가 있는 경우
➰ 자바스크립트에는 없고 타입스크립트에만 있는 기능
𝟲. 상속
✔️ 상속을 하면 생성자를 포함하여 부모 클래스가 가진 모든 것을 자동으로 가져온다.
class ITDepartment extends Department {
constructor(id: string, admins: string[]) {
super(id, 'IT');
// 부모 클래스의 생성자를 호출한다.
// id와 name을 가져오는데, name을 'IT'로 하드코딩 해준다.
this.admins = admins;
}
}
𝟳. 속성 및 '보호된' 수정자 재정의
✔️ private 속성은 정의된 클래스 내에서만 접근 가능하며, 해당 클래스로부터 상속받는 클래스에서는 접근 불가능 하다.
✔️ protected : 상속받는 클래스에서 접근할 수 있도록 하면서도 외부에서 변경 불가능한 속성
➰ 부모가 가진 메서드는 고유 기능 + 추가한 기능 을 적용해서 구현할 수 있다.
𝟴. 게터 & 세터
✔️ get : 값을 가지고 올 때 함수나 메소드를 실행하는 속성
➰ 꼭 무언가를 반환하게 만들어야 한다.
✔️ set : 값을 설정하거나 추가할 때 사용하는 속성
class AccountingDepartment extends Department {
private lastReport: string;
// private라서 .으로 값을 가져올수는 없지만 밑의 get을 통해 값을 받을 수 있다.
get mostRecentReport() {
if(this.lastReport) {
return this.lastReport;
}
throw new Error('No report found.');
}
get mostRecentReport(value: string) {
if(!value) {
throw new Error('Please pass in a valid value!');
}
this.addReport(value);
}
constructor(id: string, private reports: string[]) {
super(id, 'Accounting');
this.lastReport = reports[0];
}
addEmployee(name: string) {
if(name === 'Max') {
return;
}
this.employees.push(name);
// Dapartment 클래스에서 employees를 protected로 지정해놔서 가능하다.
}
addReport(text: string) {
this.reports.push(text);
this.lastReport = text;
}
}
const accounting = new AccountingDepartment('d2', []);
accounting.addReport('Something...'); // report 추가
console.log(accouting.mostRecentReport); // report 값 얻기
// 메서드로 이를 실행하는 게 아니라 일반 속성처럼 접근하기 때문에 ()를 사용하지 않는다.
accounting.mostRecentReport = 'report';
// 세터는 값을 주어서 사용한다.
𝟵. 정적 메서드 & 속성
✔️ 정적 속성과 메서드를 사용해서 클래스의 인스턴스에서 접근할 수 없는 속성과 메소드를 클래스에 추가할 수 있다.
✔️ 정적 메서드 : 클래스와 연결되어 있지만, 해당 클래스의 인스턴스와는 연결되지 않아서 클래스의 객체를 생성하지 않고도 호출할 수 있다.
➰ console.log(fiscalYear) 이 아닌 console.log(Department.fiscalYear)로 접근해야 한다.
class Department {
static fiscalYear = 2020;
name: string;
private employees: string[] = [];
constructor(private readonly id: string, public name: string) { // 생성자 메서드
this.name = n;
}
static createEmployee(name: string) {
return { name: name };
}
discribe(this: Department) {
// discribe가 실행될 때 discribe 코드의 this는 Department 클래스에 기반한 인스턴스를 참조해야 한다는 뜻
console.log('Department' + this.name);
}
addEmployee(employee: string) {
this.employees.push(employee);
}
printEmployeeInformation() {
console.log(this.employees.length);
console.log(this.employees);
}
}
const employee1 = Department.createEmployee('Max');
console.log(Department.fiscalYear); //2020
𝟭𝟬. 추상 클래스
✔️ 메서드 앞에 abstract가 있는 메서드가 하나 이상이라면 그 메서드를 가진 클래스 앞에도 abstract를 추가해야 한다.
➰ 추상 클래스의 추상 메서드는 메서드의 형태와 구조가 어떤지를 정의하고 있을 뿐 그 외에는 아무것도 하지 않는다.
➰ 추상 클래스를 상속받은 클래스는 추상 클래스 내에 정의된 추상 메서드를 반드시 구현해야 한다.
➰ 추상 클래스는 인스턴스화 할 수 없으며 그저 상속되어야 할 클래스일 뿐이다.
// 추상 클래스
abstract class Project {
public project_name:string|null = null;
private budget:number = 2000000000; // 예산
// 추상 메서드 정의
public abstract changeProjectName(name:string): void;
// 실제 메서드 정의
public calcBudget(): number {
return this.budget * 2;
}
}
// [오류]
// [ts] 추상 클래스의 인스턴스를 만들 수 없습니다.
// constructor Project(): Project
let new_project = new Project();
class WebProject extends Project {
// 추상 클래스에 정의된 추상 메서드 구현
changeProjectName(name:string): void {
this.project_name = name;
}
}
// 이 클래스는 인스턴스 생성 가능
📚 참고 자료 : https://yamoo9.gitbook.io/typescript/classes/abstract-class
𝟭𝟭. 싱글턴 & 개인 생성자
✔️ 싱글턴 패턴 : 특정 클래스의 인스턴스를 정확히 하나만 갖도록 한다.
✔️ getInstance : 이 클래스의 인스턴스가 이미 있는지 확인하고 없다면 새 인스턴스를 반환한다.
class AccountingDepartment extends Department {
private lastReport: string;
// private라서 .으로 값을 가져올수는 없지만 밑의 get을 통해 값을 받을 수 있다.
private static instance: AccountingDepartment;
get mostRecentReport() {
if(this.lastReport) {
return this.lastReport;
}
throw new Error('No report found.');
}
get mostRecentReport(value: string) {
if(!value) {
throw new Error('Please pass in a valid value!');
}
this.addReport(value);
}
constructor(id: string, private reports: string[]) {
super(id, 'Accounting');
this.lastReport = reports[0];
}
static getInstance() {
if(this.instance) {
// 인스턴스가 이미 있는 경우
return this.instance;
}
// 없는 경우 새 인스턴스를 생성한다.
this.instance = new AccountingDepartment('d2', []);
return this.instance;
}
addEmployee(name: string) {
if(name === 'Max') {
return;
}
this.employees.push(name);
// Dapartment 클래스에서 employees를 protected로 지정해놔서 가능하다.
}
addReport(text: string) {
this.reports.push(text);
this.lastReport = text;
}
}
const accounting = AccountingDepartment.getInstance();
// 여기서는 인스턴스를 생성하고
const accounting2 = AccountingDepartment.getInstance();
// 여기서는 이미 있는 인스턴스를 반환해준다.
𝟭𝟮. 인터페이스
✔️ 인터페이스 : 객체의 구조를 설명하는데 사용한다.
➰ 구체적인 값이 아닌 구조만 있을 뿐이다.
➰ 인터페이스 내에 존재하는 메서드는 무조건 public abstract로 선언되며, 이를 생략할 수 있다.
interface Person {
name: string;
age: number;
greet(phrase: string): void;
}
let user1: Person;
user1 = {
name: 'Max',
age: 30,
greet(phrase: string) {
console.log(phrase + ' ' + this.name);
}
};
user1.greet('Hi there - I am');
// Hi there - I am Max
➰ user1은 인터페이스 정의를 충족하기 때문에 유효한 객체이다.
𝟭𝟯. 클래스와 인터페이스 사용하기
✔️ 무언가를 인터페이스로 정의하는 경우, 분명 객체의 구조를 정의하는 데 쓰일 것이다.
✔️ 사용자 정의 타입으로도 객체 유형을 정의할 수 있는데, 클래스 내에 인터페이스를 구현하는 것이다.
-> 클래스를 선언할 때 인터페이스를 implements 해주는 것
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);
}
}
➰ 이 경우 인터페이스는 서로 다른 클래스 간의 기능을 공유하기 위해 사용된다.
➰ 인터페이스는 구현 세부 사항이 전혀 없는 반면, 추상 클래스는 꼭 구현해야 하는 부분이 있어 내가 구현한 부분을 혼동할 수 있다는 단점이 있다.
➰ 인터페이스를 사용하면 객체나 클래스에 대한 모든 것을 알 필요가 없는 유연한 코드를 작성할 수 있다.
➰ 인터페이스 내에 readonly는 추가할 수 있지만 public, private는 지정할 수 없다.
➰ 클래스에는 따로 readonly를 추가하지 않아도, 인터페이스를 클래스에 구현하고 나면 클래스에 있는 속성도 readonly가 된다.
𝟭𝟰. 인터페이스 확장하기
interface Named {
readonly name: string;
}
interface Greetable extends Named {
greet(phrase: string): void;
}
➰ Greetable 인터페이스가 Named 인터페이스가 정의하는 모든 요소를 입력하도록 요구하지는 않는다.
➰ 하지만 Greetable을 가진 클래스는 Named 인터페이스가 정의하는 모든 요소를 구현해야 한다.
➰ 인터페이스는 다수의 인터페이스의 상속을 받을 수 있다.
𝟭𝟱. 함수 타입으로서의 인터페이스
interface User {
name: string;
height: number;
}
interface GetUserName {
(user: User): string;
}
const getUserName: GetUserName = function (user) {
return user.name;
};
➰ 타입스크립트는 이 인터페이스를 함수 타입으로 사용하고자 하는 경우 함수의 형태가 이와 같다는 것을 이해한다.
𝟭𝟲. 선택적 매개변수 & 속성
✔️ '?'를 사용해서 선택적 매개변수와 속성을 만들 수 있다.
interface Named {
readonly name: string;
}
interface Greetable extends Named {
greet(phrase: string): void;
}
class Person implements Greetable {
name?: string;
age = 30;
constructor(n?: string) {
if(n) {
this.name = n;
}
}
}
𝟭𝟳. 자바스크립트로 인터페이스 컴파일
✔️ js에는 인터페이스 기능이 없기 때문에 변환 된 js 파일에서는 볼 수 없다.
➰ 개발 하는 동안 구조화된 코드를 작성하는 데 도움이 되는 순수 개발 기능이다.
➰ 컴파일 도중에 코드를 검사하는 데만 사용된 다음 무시된다.
💡 새로 알게 된 점
1. 원래부터 알고있던 개념이지만, 이 부분은 옛날부터 이해가 확 되는 느낌이 안든다ㅠㅠ 그냥 이런 개념이구나, 이렇게 쓰는구나 정도?
2. 약식 초기화라는 개념, 구현 방법
✏️ 발표
추상클래스 | 인터페이스 | |
정의? | 클래스 내 추상메서드가 하나 이상 포함되어 있거나, abstract로 정의된 경우 |
모든 메서드가 추상메서드 |
목적 | 그 추상 클래스를 상속받아서 기능을 이용하고, 확장시키는 것 |
인터페이스 내의 메서드 구현을 강제하기 위해서 |
공통점 | 상속받는 클래스, 혹은 구현하는 인터페이스 안에 있는 추상 메서드를 구현하도록 하는 것 |
'CodeStates > TS 스터디' 카테고리의 다른 글
Section 7. 제네릭 (0) | 2023.02.22 |
---|---|
Section 6. 고급 타입 (0) | 2023.02.21 |
Section 4. 차세대 자바스크립트와 TypeScript (0) | 2023.02.17 |
Section 3. TypeScript 컴파일러(및 구성) (0) | 2023.02.15 |
Section 2. TypeScript 기본 & 기본 타입 (0) | 2023.02.15 |