처음부터 차근차근

[Typescript] Class 본문

Language/Typescript

[Typescript] Class

HangJu_95 2024. 1. 2. 23:20
728x90

Typescript Class와 Javascript Class의 차이점

  • Javascript에는 접근 제어자가 없습니다.
    • But 최신 브라우저와 최신 Node.js(버전 12 이상)에서는 지원합니다.
  • Javascript에는 추상 클래스가 없습니다.
  • Javascript에는 인터페이스가 없습니다. 따라서 다중 구현이 불가능합니다.

Typescript에서는 이 부분이 보완되어 조금 더 강력하게 객체 지향 코드를 구현할 수 있게 되었습니다.

Class 속성 접근 제어자

  • public : 클래스 외부에서 접근 가능 (기본 값으로 생략 가능합니다.)
  • private : 해당 클래스 내부에서만 접근 가능합니다.
  • protected : Book 클래스를 포함한 서브(자식) 클래스에서만 접근 가능합니다.
class Book {

  // 제목
  // public: 클래스 외부에서 접근 가능
  public title:string;

  // 저자
  // public은 기본 값으로 생략 가능합니다.
  author:string;

  // 제조 공장
  // private: Book 클래스 내부에서만 접근 가능
  private _manufacturing_plant:string;

  // 종이 유형
  // protected: Book 클래스를 포함한 서브 클래스에서만 접근 가능
  protected paper_type:string;

  // constructor() 매개변수 앞에
  // public, private, protected를 사용하면
  // Book 클래스의 타입(type, 속성)을 별도 선언하지 않아도 됩니다.
  constructor(title:string, author:string, public pages:number) {
    this.title = title;
    this.author = author;
    this.pages = pages;
  }

}

/* 인스턴스 생성 ------------------------------------------------ */

let indRevo = new Book('한 권으로 정리하는 4차 산업혁명', '최진기', 367);
console.log(indRevo); // Book {}

 

또한 constructor() 매개변수 앞에 접근 제어자를 사용하면, 속성을 별도로 선언하지 않아도 됩니다.

접근 제어 메서드 설정

속성과 마찬가지로 메서드 또한 접근 제어자를 사용해 외부에서의 접근을 제어할 수 있습니다.

class Book {

  public    title:string;
  public    author:string;
  public    pages:number = 150;
  private   _manufacturing_plant:string = '충무로 공장';
  protected paper_type:string = '밍크지';

  constructor(title:string, author:string, pages:number) {
    this.title  = title;
    this.author = author;
    this.pages  = pages;
  }

  /* 메서드 ------------------------------------------------ */

  // public 메서드
  // 클래스 외부에서 접근 가능
  public printPages(): string {
    return `${this.pages}페이지`;
  }

  // protected 메서드
  // Book 클래스를 포함한 서브 클래스에서만 접근 가능
  protected changePaperType(type:string): void {
    this.paper_type = type;
  }

  // private 메서드
  // Book 클래스 내부에서만 접근 가능
  private setManufacturingPlant(plant:string): void {
    this._manufacturing_plant = plant;
  }


  /* 클래스 내부 메서드에서 private, protected 메서드 접근 가능 */

  public setPaperType(type:string):void {
    // protected 메서드 접근 가능
    this.changePaperType(type);
    console.log(this.paper_type);
  }

  public setPlant(plant:string):void {
    // private 메서드 접근 가능
    this.setManufacturingPlant(plant);
    console.log(this._manufacturing_plant);
  }

}


/* 인스턴스 생성 ------------------------------------------------ */

let indRevo = new Book('한 권으로 정리하는 4차 산업혁명', '최진기', 367);

console.log(indRevo.printPages()); // '367페이지'

// [오류]
// [ts] 'changePaperType' 속성은 보호된 속성이며
// 'Book' 클래스 및 해당 하위 클래스 내에서만 액세스할 수 있습니다.
// (method) Book.changePaperType(type: string): void
console.log(indRevo.changePaperType('인디언지'));

// [오류]
// [ts] 'setManufacturingPlant' 속성은 private이며
// 'Book' 클래스 내에서만 액세스할 수 있습니다.
// (method) Book.setManufacturingPlant(plant: string): void
console.log(indRevo.setManufacturingPlant('파주 공장'));

 

상속

Typescript는 ES6에서와 마찬가지로 클래스 상속에 extends 키워드를 사용합니다.

// Book 수퍼 클래스를 상속 받은 E_Book 클래스
class E_Book extends Book {
  // paper_type 오버라이딩
  paper_type = '스크린';
}

Constructor, super

일반적으로 부모 클래스를 상속받은 자식 클래스는 부모 클래스의 기능에 더하여 좀 더 많은 기능을 갖도록 설계합니다. constructor()를 사용해 상속 받은 부모 클래스의 생성자를 자식 클래스의 생성자로 덮어쓸 수 있습니다. 이때 반드시 super()를 사용해 부모 클래스의 생성자에 요구되는 인자를 전달해야 합니다.

class E_Book extends Book {
  paper_type = '스크린';
  constructor(
    title:string, 
    author:string, 
    pages:number, 
    public is_downloadable:boolean
  ){
    // 수퍼 클래스 constructor를 덮어쓰기 위해서는 super() 실행이 필요합니다.
    super(title, author, pages);
    this.is_downloadable = is_downloadable;
  }
}

부모 클래스의 protected 속성은 서브 클래스에서 접근 가능하지만, private 속성은 접근이 불가능합니다.

class E_Book extends Book {

  constructor(
    title:string, 
    author:string, 
    pages:number, 
    public is_downloadable:boolean
  ){

    super(title, author, pages);
    this.is_downloadable = is_downloadable;

    // 수퍼 클래스의 protected 속성은 접근 가능
    console.log(this.paper_type);

    // 수퍼 클래스의 private 속성은 접근 불가능
    // [오류]
    // [ts] '_manufacturing_plant' 속성은 private이며 'Book' 클래스 내에서만 액세스할 수 있습니다.
    // (property) Book._manufacturing_plant: string
    console.log(this._manufacturing_plant);

  }

}

getter/setter

비공개로 설정할 필요가 있는 속성은 private로 설정한 후, 이 속성에 접근하여 값을 읽거나, 쓰기 위한 Getter, Setter 함수를 사용하여 속성을 정의할 수 있습니다.

참고 : 자바스크립트에서 비공개 속성 변수 앞에는 _(언더스코어)를 붙여 비공개 속성이라고 정의합니다.
class Plant {

  // 비공개 속성 '종(Species)'
  private _species:string|null = null;

  // getter 함수
  get species(): string {
    return this._species;
  }

  // setter 함수
  set species(value:string) {
    if ( value.length > 3 ) { this._species = value; }
  }

}


/* 인스턴스 생성 ------------------------------------------------ */

let plant = new Plant();

console.log(plant.species); // null

plant.species = '줄기';

console.log(plant.species); // null

plant.species = '푸른 식물';

console.log(plant.species); // '푸른 식물'

아니면 자바처럼 이렇게 해도 됩니다.

class Plant {

  // 비공개 속성 '종(Species)'
  private species:string|null = null;

  // getter 함수
  get getSpecies(): string | null {
    return this.species;
  }

  // setter 함수
  // 참고로 set에는 타입을 지정해주지 않습니다.
  // void도 적용 불가능합니다.
  set setSpecies(value:string) {
    if ( value.length > 3 ) { this.species = value; }
  }
}

JavaScript(ES5)로 컴파일된 코드를 살펴보면 Object.defineProperty()를 사용하여 get, set 함수를 사용하여 동일하게 작동하도록 처리한 것을 확인할 수 있습니다.

var Plant = /** @class */ (function () {
  function Plant() {
    this._species = 'Default';
  }
  Object.defineProperty(Plant.prototype, "species", {
    // getter
    get: function () {
      return this._species;
    },
    // setter
    set: function (value) {
      if (value.length > 3) {
        this._species = value;
      }
    },
    enumerable: true,
    configurable: true
  });
  return Plant;
}());


/* 인스턴스 생성 ------------------------------------------------ */

var plant = new Plant();

console.log(plant.species); // null

plant.species = '줄기';

console.log(plant.species); // null

plant.species = '푸른 식물';

console.log(plant.species); // '푸른 식물'

Static 속성, 메서드

정적 속성, 메서드라고도 하며, 클래스를 통해 인스턴스를 생성할 필요 없이, 클래스의 속성 또는 메서드를 사용하고자 한다면 static 키워드를 사용해 속성, 메서드를 정의합니다.

class Mathmatics {

  // 스태틱 속성
  static PI:number = Math.PI;

  // 스태틱 메서드
  // circumference = 둘레(원주)
  static calcCircumference(radius:number) :number {
    return this.PI * radius * 2;
  }

  static calcCircleWidth(radius:number): number {
    return this.PI * Math.pow(radius, 2);
  }

}

// radius = 반지름
let radius = 4;

console.log('PI(원주율) = ', Mathmatics.PI);
console.log(`반지름이 ${radius}인 원의 넓이: πr² = `, Mathmatics.calcCircleWidth(radius));
console.log(`반지름이 ${radius}인 원의 둘레: 2πr = `, Mathmatics.calcCircumference(radius));

추상 클래스

추상화

추상 클래스를 정의할 때는 class 앞에 abstract라고 표기합니다. 또한 추상 메서드를 정의할 때도 abstract를 메서드 이름 앞에 붙입니다. 추상 메소드는 정의만 있을 뿐 몸체(body)가 구현되어 있지 않습니다. 몸체는 추상 클래스를 상속하는 클래스에서 해당 추상 메소드를 통해 필히 구현해야 합니다.

그리고 추상 클래스는 추상 메서드 뿐만 아니라, 실 사용이 가능한 메서드도 정의할 수 있습니다. 추상 클래스를 상속하는 클래스를 통해 생성된 인스턴스는 이 메서드를 사용할 수 있습니다. 추상 클래스는 말 그대로 추상이므로 클래스와 달리 인스턴스를 생성하지 않습니다. 생성 구문을 사용하면 오류가 발생합니다.

// 추상 클래스
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();

Project 추상 클래스를 상속 받은 WebProject 클래스는 추상 클래스 내에 정의된 추상 메서드를 반드시 구현해야 합니다. 구현하지 않을 경우 다음과 같은 오류 메시지를 출력합니다.

// 클래스 ⟸ 추상 클래스 상속
class WebProject extends Project {
  // [오류]
  // [ts] 비추상 클래스 'WebProject'은(는) 'Project' 클래스에서 상속된
  // 추상 멤버 'changeProjectName'을(를) 구현하지 않습니다.
  // class WebProject
}

그러므로 추상 클래스를 상속하는 클래스는 추상 클래스에 정의된 메서드를 반드시 구현해야 합니다.

class WebProject extends Project {

  // 추상 클래스에 정의된 추상 메서드 구현
  changeProjectName(name:string): void {
    this.project_name = name;
  }

}


/* 인스턴스 생성 ------------------------------------------------ */

let new_project = new WebProject();

console.log(new_project.project_name); // null

new_project.changeProjectName('CJ 올리브 네트웍스 웹사이트 개편');

console.log(new_project.project_name); // 'CJ 올리브 네트웍스 웹사이트 개편'

싱글톤 패턴

private 접근 제어자를 사용해 constructor() 앞에 붙이면 new 키워드를 통해 인스턴스를 생성하지 못하도록 제한할 수 있습니다. 대신 공개된 스태틱 메서드 getInstance()를 통해 오직 한 번만 인스턴스를 생성할 수 있습니다. 이를 싱글턴 패턴이라 부릅니다.

class OnlyOne {

  private static instance: OnlyOne;

  public name:string;

  // new 클래스 구문 사용 제한을 목적으로
  // constructor() 함수 앞에 private 접근 제어자 추가
  private constructor(name:string) {
    this.name = name;
  }
  
  // 오직 getInstance() 스태틱 메서드를 통해서만
  // 단 하나의 객체를 생성할 수 있습니다.
  public static getInstance() {
    if (!OnlyOne.instance) {
      OnlyOne.instance = new OnlyOne('싱글턴 객체');
    }
    return OnlyOne.instance;
  }
  
}


/* 인스턴스 생성 ------------------------------------------------ */

// [오류]
// [ts] 'OnlyOne' 클래스의 생성자는 private이며 클래스 선언 내에서만 액세스할 수 있습니다.
// constructor OnlyOne(name: string): OnlyOne
let bad_case = new OnlyOne('오류 발생');

let good_case = OnlyOne.getInstance();

읽기 전용 속성

readonly 키워드를 사용해 클래스 속성 이름 앞에 추가하면 읽기 전용 속성이 되어 속성을 다른 값으로 쓸 수 없습니다. 다른 값을 설정하려고 시도하면 컴파일 과정에서 다음과 같은 오류 메시지를 출력합니다.

class OnlyOne {

  private static instance:OnlyOne;

  // 읽기 전용 속성 설정
  public readonly name:string;

  private constructor(name:string) {
    this.name = name;
  }

  public static getInstance(name:string):OnlyOne {
    if (!OnlyOne.instance) {
      OnlyOne.instance = new OnlyOne(name);
    }
    return OnlyOne.instance;
  }

}


/* 인스턴스 생성 ------------------------------------------------ */

let special_one = OnlyOne.getInstance('스페셜 원');

console.log(special_one.name);

// [오류]
// [ts] 상수 또는 읽기 전용 속성이므로 'name'에 할당할 수 없습니다.
// (property) OnlyOne.name: string
special_one.name = '노멀 원';

참고

 

클래스 - TypeScript Guidebook

let indRevo = new Book('한 권으로 정리하는 4차 산업혁명', '최진기', 367);

yamoo9.gitbook.io

 

Documentation - Classes

How classes work in TypeScript

www.typescriptlang.org