타입스크립트 Section 5 : 클래스 & 인터페이스

BRANDY·2023년 3월 5일
0

클래스

클래스는 객체의 청사진으로 데이터를 저장하고 메서드를 실행하기 위해 메서드를 저장하는데 사용하는 데이터 구조이다. 이를 이용하여 객체의 형태, 데이터, 메소드를 정의할 수 있으며 동일한 구조 및 기능을 하는 객체들을 재사용 및 복사할 수 있다.

class Department {
    name: string;
    
    constructor(n: string) {	
        this.name = n;
    }
}

클래스는 첫글자를 대문자로 작성하며 생성자 메서드(예약어)를 포함한다. 생성자를 통해 객체를 초기화하며 클래스 및 객체에 연결된다.

const accounting = new Department('Accounting');
console.log(accounting);
// Department {name:"Accounting"}

생성자 함수 / this

클래스 속성과 메소드를 참조하기 위해 this를 사용한다. 이는 자바스크립트에서 배웠던 this 사용을 참고하자.

class Department {
    name: string;
    
    constructor(n: string) {	
        this.name = n;
    }
    
    discribe(this: Department) {
    // 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();

개인 및 공용 액세스 수정자

클래스는 하나의 방법으로만 사용하며 클래스 외부에서의 다른 접근을 막아야 하므로 private(생성된 객체 내부에서만 접근)를 사용한다.
또한 private키워드가 붙은 프로퍼티는 _(언더바)를 붙이는것이 통상적이라고 한다. 후에 외부에서 접근할때는 get / set를 이용한다.

class Department {
  
  private employees: string[] = [];


  addEmployee(employee: string) {
    this.employees.push(employee);
  }
}

public은 디폴트 값이며 별도로 메서드에 추가하지 않아도 된다. 단, 명시적으로 다음과 같이 수정할 수 있다.

class Input {
    public name: string;
    public constructor(name: string) {
        this.name = name;
    }
    public inputName() {
        console.log(`input name is ${ this.name }`);
    }
}
class Button extends Input {
    public constructor(name: string) {
        super(name);
    }
    public inputName() {
        console.log(`button name is ${ this.name }`);
    }
}

protected는 선언한 클래스를 포함하여 상속받는 하위클래스에서만 접근 가능.

약식 초기화

생성자 함수 내에 이중초기화 코드를 입력하고 필드 정의를 제거함으로써 필드를 찾은 다음 값을 저장해야 하는 이중 초기화 코드를 한 번에 처리하도록 축약한다.

//기존 코드
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 속성이 생성된 클래스에 생성될 수 있도록 해야 한다.
    }
}

읽기 전용 속성

private이나 public이어서도 안 되고 초기화 후에 변경되어서도 안 되는 특정 필드가 있는 경우 확실히 변경되지 않게 하려면 readonly를 추가한다.

constructor(private readonly id: string, public name: string)

상속

ES6 문법에서 사용하는 extends 키워드를 사용한다. 생성자를 포함 부모클래스가 가진것을 상속한다.

class ITDepartment extends Department {
    constructor(id: string, admins: string[]) {
        super(id, 'IT');
        // super로 기본(부모) 클래스의 생성자를 호출한다.
 
        this.admins = admins;
    }
}

속성 및 보호된 수정자 재정의

상위 클래스에서 정의한 메서드를 자식클래스에서 재정의 할 수 있다.
private : 정의된 클래스 내에서만 접근 가능하며 해당 클래스로부터 상속받는 클래스에서는 접근 불가능한 속성
protected : 상속받는 클래스에서 접근할 수 있지만 외부에서는 변경 불가능한 속성

class AccountingDepartment extends Department {
  constructor(id:string, private reportes:string[]){
    super(id, 'Accounting');
  }
  addEmployee(name:string){
    this.employees.push(name)
  }
}

게터 & 세터

로직을 캡슐화 하고 속성을 읽거나 설정하려할 때 실행되어야 하는 추가적인 로직을 추가할때 유용하다.

게터
get 키워드를 사용하여 값을 가지고 올때, 함수나 메소드를 실행하는 속성으로 private로 지정된 값에 접근할 수 있고 반드시 무언가를 반환해야하기 때문에 return 문 필요

class AccountingDepartment extends Department {
  private lastReport:string;

  get mostRecentREport(){
    if(this.lastReport){
      return this.lastReport;
    }
    throw new Error('No report found.')
  }

  constructor(id:string, private reportes:string[]){
    super(id, 'Accounting');
    this.lastReport = reports[0]
  }
...
}
const accounting = new AccountingDepartment('d2',[]);
console.log(accounting.mostRecentReport)

세터
set 키워드를 사용하여 값을 설정하고 추가할때 사용하는 속성

class AccountingDepartment extends Department {
  private lastReport:string;

  get mostRecentReport(){
    if(this.lastReport){
      return this.lastReport;
    }
    throw new Error('No report found.')
  }

  set mostRecentReport(value:string){
    if(!value){
      throw new Error('Please pass in a valid value!')
    }
    this.addReport(value)
  }
...
}
const accounting = new AccountingDepartment('d2',[]);
accounting.mostRecentReport = ''; 

정적 메서드 & 속성

정적 메서드와 속성을 사용해서 클래스의 인스턴스에서 접근할 수 없는 속성 및 메소드를 클래스에 추가할 수 있다
새 키워드 없이 직접 클래스에서 호출하며 그룹화 하거나 클래스에 매핑하려는 유틸리티 함수 혹은 클래스에 저장하고자 하는 전역상수에 사용한다.

static 키워드를 붙여 정적 메소드를 만들고 인스턴스 생성 없이도 호출할 수 있다.

class Student {
    constructor(protected name: string) {}

    static printInfo(): void {
        console.log(`이름: ${this.name}`);
    }
}

추상클래스

메서드의 형태와 구조를 정의하지만 body가 구현되어 있지 않은 클래스
추상 메서드는 abstract키워드를 붙여 사용하며 추상 클래스에서만 사용할 수 있기 때문에 추상 클래스를 상속받은 클래스는 클래스내에 추상 속성이 필요하다. 또한 추상 클래스로는 객체 인스턴스를 생성할 수 없고 상속용으로만 가능하다.

abstract describe(this:Department) : void;

@ 클래스와 인스턴스의 차이?
인터페이스는 모든 메서드가 추상 메소드이며 추상 클래스는 추상 메소서드만 포함하는 것이 아니라 실제 구현이 있는 메서드도 포함할 수 있다.

싱글톤 & 개인 생성자

특정 클래스의 인스턴스를 정확히 하나만 갖도혹 하며 정적 메소드나 속성을 사용할 수 없거나 사용하지 않고자 하는 동시에 항상 클래스를 기반으로 정확히 하나의 객체만 가지도록 하는 경우에 사용

인터페이스

객체의 타입을 정의할 수 있는 interface키워드이며 프로퍼티와 메소드를 가질 수 있다는 점에서 클래스와 유사하지만 직접 인스턴스를 생성할 수 없는 추상메소드이다.

인터페이스 선언

interface 인터페이스 이름{
      property name[?]: property type[,...]
}

객체의 타입을 정의하는 것이 목적이기 때문에 {}로 프로퍼티 이름과 타입을 나열하는 형태로 사용

interface Person {
  name:string;
  age:number;

  greet(phrase:string):void;
}

let user1 : Person;
user1 = {
  name:'ABC',
  age:20,
  greet(phrase:string){
    console.log(phrase)
  }
}

클래스와 인터페이스 사용하기

인터페이스를 정의할때는 객체의 구조를 정의하는데 사용한다.
또한 아래의 예시는 서로 다른 클래스의 기능을 공유하기 위해 사용되는데 추상클래스의 구현부 내용과 내가 작성한 부분을 혼동하지 않도록 주의한다.

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는 사용할 수 없다.

인터페이스 확장하기

interface Named {
    readonly name: string;
}

interface Greetable extends Named {
    greet(phrase: string): void;
}

extends를 사용하여 둘 이상의 인터페이스를 하나로 병합(확장)할 수 있다.

함수 타입의 인터페이스

인터페이스는 함수의 타입으로도 사용할 수 있으며 타입이 선언된 파라미터의 리스트와 리턴타입을 정의하고 함수는 이를 준수해야한다.

type AddFn = (a:number, b:number)=>number;

interface AddFn {
  (a:number, b:number):number;
}

인터페이스로 정의한 함수를 사용할 때는 익명함수를 사용한다.

let add : AddFn;
add = (a:number, b:number) => {
  return a+b;
}

선택적 매개변수 & 속성

속성 뒤에 '?'를 추가하여 선택적 매개변수와 속성을 만들 수 있다

interface Named {
  name:string;
  outputName?:string;
}

인터페이스에서는 선택적 속성을 입력하고 클래스에서는 선택적이지 않은 속성을 입력했다면 로직이 항상 초기화되도록 해야한다.

interface Named {
  name?:string;
}
class Person implements Named{
  name:string;
  constructor(n:string){
    this.name = n;
    if(n){
      this.name = n;
    }
  }
}
profile
프런트엔드 개발자

0개의 댓글