Dandy Now!
  • [TypeScript] 유데미 강의 "Typescript :기초부터 실전형 프로젝트까지 with React + NodeJS" 정리(섹션 5: 클래스 & 인터페이스)
    2024년 01월 24일 11시 19분 08초에 업로드 된 글입니다.
    작성자: DandyNow
    728x90
    반응형

    섹션 5: 클래스 & 인터페이스

    📌 OOP(Object-oriented Programming)

    코드에서 실제 사물과 최대한 유사한 객체를 사용해 코드를 이해하기 쉽도록 만드는 것!

     

    📌 class == blueprint

    청사진: 아키텍처 또는 공학 설계를 문서화한 기술 도면을 인화로 복사하거나 복사한 도면

    🤔 class와 instance의 관계에 대한 내 생각
    붕어빵틀과 붕어빵, 필름과 사진, 도장과 도장 찍기

     

    📌 this

    class Department {
      name: string; // 필드라고 함. 객체가 아님.
    
      // 생성자: 생성하는 객체(인스턴스)의 초기화 작업
      constructor(n: string) {
        this.name = n;
      }
    
      describe() {
        console.log("Department: " + this.name);
      }
    }
    
    const accounting = new Department("Accounting"); // 키와 값을 가진 객체 생성
    
    accounting.describe(); // 출력 결과: Department: Accounting
    
    const accountingCopy = { describe: accounting.describe };
    
    accountingCopy.describe(); // 출력 결과: Department: undefined
    // this가 가리키는 accountingCopy에 name 속성이 없기 때문

    위의 코드를 보면 accounting과 accountingCopy 객체의 describe 메서드의 출력 결과가 다른 것을 볼 수 있다. 그 이유는 this가 각각 다른 객체를 가리키고 있기 때문이다. 아래 코드는 this 문제를 해결한 코드이다.

    class Department {
      name: string;
    
      constructor(n: string) {
        this.name = n;
      }
    
      //   describe() {
      //     console.log("Department: " + this.name);
      //   }
      // this를 인수로 넣고 타입을 Department 클래스로 하였다.
      // 이 인수를 넣지 않아도 Department: DUMMY를 출력하지만 name에 string 타입이 아닌 다른 타입도 입력이 가능하기 때문에 인수와 타입을 지정하였다.
      describe(this: Department) { 
        console.log("Department: " + this.name);
      }
    }
    
    const accounting = new Department("Accounting"); // 키와 값을 가진 객체 생성
    
    console.log(accounting.name);
    
    accounting.describe(); // Department: Accounting
    
    // const accountingCopy = { name: 123, describe: accounting.describe }; // Department의 name의 타입이 string이기 때문에 name 속성의 값으로 숫자가 들어 올 수 없음
    const accountingCopy = { name: "DUMMY", describe: accounting.describe }; // name 속성에 string 타입의 값을 추가
    
    accountingCopy.describe(); // Department: DUMMY

     

    📌 접근 제한자

    접근 제한자는 public, private가 있다. public은 default이기 때문에 생략 가능하다. 접근 제한자 public, protected, private은 자바스크립트에는 없는 타입스크립트 문법이다.

    class Department {
      private employees: string[] = []; // 접근 제한자 private 적용
    
      addEmployee(employee: string) {
        this.employees.push(employee);
      }
    
      printEmployeeInformation() {
        console.log("직원 수 : ", this.employees.length);
        console.log("직원 명단 : ", this.employees);
      }
    }
    
    const accounting = new Department();
    
    accounting.addEmployee("김일남");
    accounting.addEmployee("김이남");
    
    // accounting.employees[2] = "Anna"; // 접근 제한자 private 때문에 employees에 직접 접근할 수 없음
    
    accounting.printEmployeeInformation();

     

    📌 약식 생성자

    생성자는 아래 코드와 같이 약식으로 작성이 가능하다.

    // 주석 처리된 부분은 생성자 약식 표현 전 코드임
    class Department {
      // private id: string;
      // private name: string;
    
      constructor(private id: string, public name: string) {
        // this.id = id;
        // this.name = n;
      }
    }

     

    📌 "읽기 전용" 속성

    readonly는-자바스크립트에서는 지원하지 않는-타입스크립트에서만 지원하는 기능이다.

    class Department {
      // 생성자의 인수 id에 읽기 전용 속성 적용
      constructor(private readonly id: string, public name: string) {
      }
    }

     

    📌 상속

    class Department {
      constructor(private readonly id: string, public name: string) {
      }
    }
    
    class ITDepartment extends Department {
      // 상속하면 super 키워드로 부모 클래스의 필드를 상속할 수 있다.
      // 이 예제에서는 생성자의 인수로 name 속성이 빠져있음을 유의하라!
      // name 속성이 빠져있지만 ITDepartment 클래스는 name 속성을 가지고 있기 때문에 super에서 name을-"IT"로-하드코딩하였다.
      constructor(id: string, admins: string[]) {
        super(id, "IT"); // 문자열 "IT" 주의
      }
    }

     

    📌 메서드 오버라이딩과 protected

    class Department {
      // private 적용된 필드는 상속 받은 클래스에서는 사용할 수 없다.
      // protected는 외부의 액세스를 제한하면서도 "상속 받은 클래스"에서 사용할 수 있다.
      protected employees: string[] = []; // 접근 제한자를 protected로 변경
    
      constructor(private readonly id: string, public name: string) {}
    
      addEmployee(employee: string) {
        this.employees.push(employee);
      }
    
      printEmployeeInformation() {
        console.log(this.employees.length);
        console.log(this.employees);
      }
    }
    
    class AccountingDepartment extends Department {
      constructor(id: string, private reports: string[]) {
        super(id, "Accounting");
      }
    
      // 메서드 오버라이딩
      addEmployee(name: string) {
        if (name === "김일남") {
          return;
        }
        this.employees.push(name);
      }
    }
    
    const accounting = new AccountingDepartment("d2", []);
    
    // employees 필드의 접근 제한자를 protected로 변경했기 때문에 추가 가능
    accounting.addEmployee("김일남");
    accounting.addEmployee("김이남");
    
    accounting.printEmployeeInformation();

     

    📌 getter, setter

    getter, setter는-자바스크립트에는 없는-타입스크립트 문법이다.

    class Department {
      constructor(private readonly id: string, public name: string) {}
    }
    
    class AccountingDepartment extends Department {
      private lastReport: string;
    
      // get, set 키워드를 이용함
      // getter
      get mostRecentReport() {
        if (this.lastReport) {
          return this.lastReport;
        }
        throw new Error("No report found.");
      }
    
      // setter
      set 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];
      }
    
      addReport(text: string) {
        this.reports.push(text);
        this.lastReport = text;
      }
    }
    
    const accounting = new AccountingDepartment("d2", []);
    
    // accounting.mostRecentReport = ""; // Uncaught Error: Please pass in a valid value!
    accounting.mostRecentReport = "Year End Report"; // setter로 값 변경
    console.log(accounting.mostRecentReport); // getter로 접근
    accounting.addReport("Something went wrong..."); // 메서드로도 접근 가능
    console.log(accounting.mostRecentReport);

     

    📌 static property, static method

    class Department {
      static fiscalYear = 2024; // static property
      constructor(private readonly id: string, public name: string) {
        // console.log(this.fiscalYear); // this로 접근할 수 없음
        console.log(Department.fiscalYear); // 2024
      }
      static createEmployee(name: string) {
        return { name };
      }
    }
    
    console.log(Department.fiscalYear); // 2024
    console.log(Department.createEmployee("김일남")); // {name: '김일남'}

     

    📌 추상 클래스

    추상 클래스는 인스턴스를 생성할 수 없고, 자식 클래스가 상속받는 클래스이다. 추상 클래스 내 추상 메서드를 자식 클래스가 오버라이딩하도록 강제한다.

    // 클래스에 abstract 키워드 붙임
    abstract class Department {
      constructor(protected readonly id: string, public name: string) {}
    
      // 오버라이딩을 강제할 메서드에 abstract 키워드 붙임
      abstract describe(this: Department): void;
    }
    
    class ITDepartment extends Department {
      admins: string[];
      constructor(id: string, admins: string[]) {
        super(id, "IT");
        this.admins = admins;
      }
    
      // 오버라이딩
      describe() {
        console.log("IT Department - ID: " + this.id);
      }
    }
    
    class AccountingDepartment extends Department {
      private lastReport: string;
    
      constructor(id: string, private reports: string[]) {
        super(id, "Accounting");
        this.lastReport = reports[0];
      }
    
      // 오버라이딩
      describe() {
        console.log("Accounting Department - ID: " + this.id);
      }
    }
    
    const it = new ITDepartment("d1", []);
    it.describe(); // IT Department - ID: d1
    
    const accounting = new AccountingDepartment("d2", []);
    accounting.describe(); // Accounting Department - ID: d2

     

    📌 싱글톤(Singleton) 패턴

    싱글톤 패턴은 특정 클래스의 인스턴스를 1개만 생성되는 것을 보장하는 디자인 패턴이다. getter를 활용하여 정의한다.

    class Department {
      constructor(protected readonly id: string, public name: string) {}
    }
    
    const d1 = new Department("A", "김일남");
    const d2 = new Department("B", "김이남");
    
    console.log(d1 === d2); // false
    
    // 싱글톤(Singleton) 패턴
    // 특정 클래스의 인스턴스를 1개만 생성되는 것을 보장하는 디자인 패턴
    class AccountingDepartment {
      // 하나의 인스턴스를 할당할 정적 필드 
      private static instance: AccountingDepartment;
    
      constructor(protected readonly id: string, public name: string) {}
    
      // 정적 메서드 getInstance를 통해서만 AccountingDepartment의 인스턴스를 생성할 수 있음
      static getInstance(id: string, name: string) {
        // 만약 기존에 생성된 인스턴스가 있다면 기존 인스턴스를 반환
        if (AccountingDepartment.instance) {
          return this.instance;
        }
        // 기존 생성된 인스턴스가 없다면 인스턴스를 생성
        this.instance = new AccountingDepartment(id, name);
        return this.instance;
      }
    }
    
    const a1 = AccountingDepartment.getInstance("C", "김삼남");
    const a2 = AccountingDepartment.getInstance("D", "김사남");
    
    console.log(a1 === a2); // true
    
    // "김사남" 인스턴스는 생성되지 않음
    console.log(a1); // AccountingDepartment {id: 'C', name: '김삼남'}
    console.log(a2); // AccountingDepartment {id: 'C', name: '김삼남'}

    📌 인터페이스

    • 인터페이스는 자바스크립트에는 없는 문법
    • 개발 전용 기능으로서 컴파일시 버려짐
    • 객체의 구조를 정의하는 데 사용
    • 객체의 구조가 인터페이스의 구조와 일치하는지 타입을 확인함
    interface Person {
      name: string;
      age: number;
    
      greet(phrase: string): void;
    }
    
    let user1: Person;
    
    user1 = {
      name: "김일남",
      age: 99,
      greet(phrase: string) {
        console.log(phrase + " " + this.name);
      },
    };
    
    user1.greet("Hi, there!"); // Hi, there! 김일남

     

    📌 인터페이스와 클래스

    • 객체의 구조를 정의하는 데 interface와 type을 모두 사용할 수 있지만 차이점이 있다.
    • 인터페이스로 정의하면 객체 구조를 정의하고자 한다는 것을 명확하게 나타낼 수 있다. 따라서 객체 구조 정의에 있어서 인터페이서가 더 적합하다.
    • 인터페이스는 클래스가 구현해야 하는 구조를 강제할 수 있다.
    interface Greetable {
      name: string;
    
      greet(phrase: string): void;
    }
    
    class Person implements Greetable {
      name: string;
      age = 99;
    
      constructor(n: string) {
        this.name = n;
      }
    
      greet(phrase: string) {
        console.log(phrase + " " + this.name);
      }
    }
    
    let user1: Greetable;
    
    user1 = new Person("김일남");
    
    user1.greet("Hi there - I am"); // Hi there - I am 김일남
    console.log(user1); // {age: 99, name: '김일남'}

     

    📌 인터페이스 상속

    interface Named {
      readonly name: string; // readonly는 읽기 전용으로서 interface, type에서 사용 가능
    }
    
    // extends 키워드로 상속 가능. 콤마(,)를 사용해 다수 인터페이스 상속 가능
    interface Greetable extends Named {
      greet(phrase: string): void;
    }
    
    // (위의 코드와 같으므로 생략)

     

    📌 함수 타입으로서의 인터페이스

    // 아래와 같은 type 키워드로 함수 타입을 정의하는 경우가 더 많이 쓰이지만
    // type AddFn = (a: number, b: number) => number;
    // interface로 함수 타입을 정의할 수 있다. 아래 예제를 보라!
    interface AddFn {
      (a: number, b: number): number;
    }
    
    let add: AddFn;
    
    add = (n1: number, n2: number) => {
      return n1 + n2;
    };
    
    console.log(add(1, 2)); // 3

     

    📌 Optional

    interface Named {
      readonly name?: string; // 선택적 속성(Optional Properties)
    }
    
    interface Greetable extends Named {
      greet(phrase: string): void;
    }
    
    class Person implements Greetable {
      name?: string; // 선택적 속성(Optional Properties)
      age = 30;
    
      constructor(n?: string) { // 선택적 매개변수(Optional Parameter)
        this.name = n;
      }
    
      greet(phrase: string) {
        if (this.name) {
          console.log(phrase + " " + this.name);
        } else {
          console.log("Hi!");
        }
      }
    }
    
    let user1: Greetable;
    user1 = new Person();
    user1.greet("Hi there - I am"); // Hi!
    console.log(user1); // {age: 30, name: undefined}

     

    728x90
    반응형
    댓글