타입스크립트랑 친해지기 2)

이병관·2022년 4월 12일
0
post-thumbnail

ADDITIONAL


10) 데코레이터 / TSC-DOC

데코레이터는 클래스 선언, 메서드, 접근자, 프로퍼티 또는 매개 변수에 첨부할 수 있는 특수한 종류의 선언입니다.

데코레이터는 @expression 형식을 사용합니다.

여기서 expression은 데코레이팅 된 선언에 대한 정보와 함께 런타임에 호출되는 함수여야 합니다.

간단하게 설명하면 다음과 같습니다.

  • 데코레이터는 함수입니다
  • 컴파일 타임에는 그 함수의 타입만 체크합니다
  • 런타임시에 사용 및 처리됩니다
  • 클레스, 메소드, 파라미터, 프로퍼티에 사용할 수 있습니다
  • 클래스가 인스턴스에 만들어 질 때가 아닌 최초 클래스가 참조될때 한번 적용됩니다.

말 그대로 함수에 무언가를 첨가하듯(Decorating), Higher-order function과 같이 어떤 코드를 다른 코드로 감싸주는 역활을 수행합니다.

데코레이터는 TypeScript의 설정을 활성화 해야 사용이 가능합니다.

tsc --target ES5 --experimentalDecorators

{
    "compilerOptions": {
        "target": "ES5",
        "experimentalDecorators": true
    }
}

Express와 NestJS

express나 NestJS를 사용해봣다면 다음과 같은 코드들이 익숙할것입니다.

//express
const router = new Router()




router.get("/api/users", getAllUsers)
router.get("/api/users/:id", getUser)
router.post("/api/users", createUser)




//////////////
//NestJS
@Controller("api")
export class UsersController {
  @Get("users")
  getAllUsers(): User[] {
    // code
  }




  @Get("users/:id")
  getUser(@Param("id") id: string): User {
    // code
  }




  @Post("users")
  createUser(@Body() body: CreateUserInput): User {
    // 

아시다시피 두 결과의 코드는 같습니다. 하지만 Decorator가 대체 어떤 존재이기에 같은 결과를 낼까요?

앞서 말한것을 다시 언급하자면,

decorator는 class 및 class 내부에서만 사용할 수 있습니다.

즉, decorator는 class, class 내부의 propertyaccessormethod,

그리고 parameter에 사용할 수 있습니다.

클래스 데코레이터

클래스 선언 부분 위에 넣어주는 데코레이터로써

이 데코레이터는 기존 클래스의 정의를 확장해주는 역할을 합니다.

클래스 데코레이터는 아래와 같은 특징이 있습니다.

  1. 클래스 위에 붙히기만 해도 인스턴스를 생성하지 않아도 실행이 된다.
  2. 인자로 Constructor Function을 가져가는데, 그 Function은 클래스의 생성자 함수입니다.
function classDecorator<T extends { new (...args: any[]): {} }>(target: T) {
  return class extends target {
    private newProperty = "new property";
    private wow = "abcd";
    constructor(...args: any) {
      super(...args);
      this.wow = "wow";
    }
  };
}
@classDecorator
class Greeter {
  property = "property";
  hello: string;
  constructor(m: string) {
    this.hello = m;
  }
}




console.log(new Greeter("world"));




/*
Greeter {
  property: 'property',
  hello: 'world',
  newProperty: 'new property',
  wow: 'wow'

위의 예시는 새로운 생성자를 return 해주는 함수입니다.

위의 코드를 실행해 보면, 기존 코드에는 없었던 wow 라는 property 도 추가되어 있는 것을 확인할 수 있습니다.

클래스 데코레이터 class decorator 는 클래스의 생성자(Constructor)를 유일한 인수로 호출합니다.


Method Decorator

메서드 데코레이터는 메서드 선언 앞에 사용됩니다.

데코레이터는 메서드 선언을 확인, 수정, 교체하는데 사용될 수 있습니다.

메서드 데코레이터 함수가 전달 받는 인자는 총 3가지로 다음과 같습니다.

  • 첫 번째 argument: classprototype
  • 두 번째 argument: class에서 해당 method의 key
  • 세 번째 argument: property descriptor

자바스크립트 객체에는 속성이 존재하며, 각 속성은 값(함수 포함)을 가집니다.

각 속성은 추가로 각 속성들이 어떻게 작동할지에 대한 정의를 가지고있는데,

이를 descriptor라 합니다.

DefineProperty()

Object.defineProperty() 정적 메서드는 객체에 새로운 속성을 직접 정의하거나 이미 존재하는 속성을 수정한 후, 해당 객체를 반환합니다.

Method Decorator Property Descriptor의 종류

function methodDecorator(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  console.log('target', target);
  console.log('propertyKey', propertyKey);
  console.log('descriptor', descriptor);
}




class Test3 {
  @methodDecorator
  public print() {}
}




// target Test3 { print: [Function] }
// propertyKey print
// descriptor { value: [Function], writable: true, enumerable: true, configurable: tr

메소드 데코레이터는 다음과 같이 정의 합니다.

  • target: 클래스
  • PropertyKey: 메소드 이름
  • descriptor: PropertyDescriptor
////PropertyDescriptor (lib.es5.d.ts)
interface PropertyDescriptor {
    configurable?: boolean; // 해당 객체로부터 그 속성을 제거할수 있는지를 알려줍니다(기본값 false)
    enumerable?: boolean; //해당 객체의 키가 열거 가능한지 기술합니다(기본값 false)
    value?: any; // 속성에 해당되는 값으로 오직 적합한 자바스크립트 값만 올수있습니다.
    writable?: boolean; // true로 설정될시, 할당연산자를 통해 값을 바꿀수 있습니다(기본값 false)
    get?(): any;
    set?(v: any): void;
}

PropertyDescriptor 조작

function methodDecoratorFactory(canBeEdit: boolean = false) {
  return function(
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    descriptor.writable = canBeEdit;
  };
}

위의 descriptor는 아래의 methodDecoratorFactory의 함수의 값입니다.

즉 아래의 first, second, third함수를 뜻합니다.

이 값을 인자로 하여 descriptor를 조작할수 있습니다.

class Test4 {
  @methodDecoratorFactory()
  first() {
    console.log('first original');
  }




  @methodDecoratorFactory(true)
  second() {
    console.log('second original');
  }




  @methodDecoratorFactory(false)
  third() {
    console.log('third original');

위의 third를 보시면 인자값으로 false를 주고있습니다.

canBeEdit이라는 이름으로 boolean값인 false를 받고,

descriptor.writable = canBeEdit(false);값이 되었으니,

const test4 = new Test4();
test4.first = function() {
  console.log('first new');
}; // runtime error
test4.second = function() {
  console.log('second new');
};// runtime error
test4.third = function() {
  console.log('third new');
};

test4.third는 새로 값이 할당 되지 않습니다.

즉 third new 이라는 값이 나오지않고 third original 라는 값이 나오게 됩니다.

test4.second는 값을 할당 할 수 있도록 true로 명시햇기에,

second new 라는 값이 나오게 됩니다.

function log(show: boolean = true) {
  return function(
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const original = descriptor.value;
    	descriptor.value = function (...args: any[]) {
			if (show) {
				console.log('start');
				original(...args);
      	        console.log('end');
			} else {
				original(...args);
			}
    };
  };
}

descriptor.value는 들어오는 함수를 지칭합니다.

즉 value는 print1 또는 print2일것입니다.

original이라는 상수로 들어온 함수를 저장한뒤,

여기서는 만약 show라는 인자가 true값일 경우,

start, end라는 콘솔 문으로 위 아래 찍어주고,

아니라면 그냥 함수를 출력하라는 데코레이터 함수를 만들수 있습니다.

class Test5 {
  @log()
  print1(first: string, second: number) {
    console.log('print1', first, second);
  }




  @log(false)
  print2(first: string, second: number) {
    console.log('print2', first, second);
  }
const test5 = new Test5();
test5.print1('mark', 36);
test5.print2('mark', 36);

Property Decorator

function propertyDecorator(target: any, propName: string) {...}




function propertyDecoratorFactory(...) {
	return function(target: any, propName: string) {...}

속성 데코레이터는 두가지 인자를 받습니다.

  • target: 클래스
  • propName: 클래스의 키

속성에 대한 설정을 할수있는 데코레이터입니다.

function propertyDecorator(target: any, propName: string): any {
  console.log(target);
  console.log(propName);
  return {
    writable: false
  };
}




class Test6 {
  @propertyDecorator
  name: string = 'Mark'; //타겟은 Test6이고 propName은 name이란 속성입니다       
}                        //허나 writable이 false이기 때문에 실질적으로 이곳에서 런타임 에러가 발생합니다.




const test6 = new Test6();
test6.name = 'Anna'; // 런타임 에

n

Parameter Decorator

파라미터 데코레이터는 메소드 데코레이터와 비슷하게 생겼습니다.

function parameterDecorator(
	target: any,
	methodName: string,
	paramIndex: number
) {...}




function parameterDecoratorFactory(...) {
	return function(
		target: any,
		methodName: string,
		paramIndex: number
	) {...}
  • target : 클래스
  • methodName : 파라미터가 속한 함수의 이름
  • paramIndex : 그 함수의 몇번째 파라미터인지
function parameterDecorator(
	target: any,
	methodName: string,
	paramIndex: number
) {
	console.log('parameterDecorator start');
  console.log(target);
  console.log(methodName);
  console.log(paramIndex);
  console.log('parameterDecorator end');
}




class Test7 {
	private _name: string;
	private _age: number;
                              //다음과 같이 적용할 파라미터의 앞에 선언합니다
	constructor(name: string, @parameterDecorator age: number) { 
		this._name = name;
		this._age = age;
	}




	print(@parameterDecorator message: string) {
		console.log(message);
	}
}




// parameterDecorator start ➝ print 함수
// Test7 { print: [Function] } 타겟의 이름(클래스)
// print 파라미터가 속한 이름(함수의 이름)
// 0 몇번째 인덱스: 0번째 인덱스
// parameterDecorator end








// parameterDecorator start ➝ constructor
// [Function: Test7] 타겟의 이름: constructor
// undefined 파라미터가 속한 이름(함수의 이름)
// 1 몇번째 인덱스: 1번째 인덱스
// parameter

파라미터 데코레이터는 클래스 내부의 어떤 속성에 validation을 걸때 경고를 사용한다거나

다른 곳에서도 사용할 수 있습니다.

참고자료: 타입스크립트의 데코레이터 살펴보기

11) 네임스페이스와 모듈

네임스페이스

네임스페이스는 연관된 코드를 논리적으로 묶는 방법입니다.

일종의 패키지 개념으로써

클래스나 인터페이스, 각종 함수 같은 내용을 한 파일에서 그룹화하여 관리 할 수 있게 해주는 개념입니다.

네임스페이스 정의는 namespace 키워드와 이름으로 시작합니다.

Typescript의 Namespace는 네임스페이스를 이용하는 내부모듈 (Internal Module)형식으로 코드를 제공합니다.

namespace MyConsole {
  export function log(msg) {
    console.log(msg);
  }
}




MyConsole.log("MyConsole")

/// 와 같이 접근할 수 있습니다.

Triple-Slash Directives

참조할 때 사용했던 /// 는 컴파일 시 그냥 주석으로 처리됩니다.

모듈

ECMAScript 2015(ES6)에 추가된 모듈 기능을 TypeScript에서도 똑같이 사용 할 수 있습니다.

모듈은 자신만의 스코프를 가지고 있으며, 모듈 내부에 선언된 변수, 함수, 클래스 등은 export 되지 않는 한 외부에서 접근 할 수 없습니다.

export된 모듈은 다른 모듈에서 import 키워드를 통해 불러올 수 있는데,

이를 가능하게 하는 것은 모듈 로더입니다.

모듈 로더는 런타임에 import된 모듈(디펜던시)의 위치를 확인한다. 자바스크립트에서 사용되는 모듈 로더의 종류는 크게 두 가지이다.
- CommonJS 모듈을 위한 Node.js의 로더
- AMD 모듈을 위한 RequireJS 로더

import, 혹은 export 키워드를 포함한 파일은 모듈로 처리됩니다.

그 외(import, export 키워드가 없는 파일)는 일반 스크립트(글로벌 스코프를 공유하는)로 처리됩니다.

내부모듈? 외부 모듈?

Typescript의 모듈 방식은 Internal Module(내부 모듈)과 External Module(외부 모듈) 두가지의 모듈이 존재합니다.

두 모듈화 방식의 차이는 module-loader의 의존성 여부입니다.

내부모듈은 TS만의 특유한 모듈 방법으로써 

다른 module-loader에 의존하지 않고 TS를 컴파일 할 때 이름이 명명된

 JS 오브젝트를 생성함으로써 모듈화합니다.

말 그대로 이름을 붙이는 네임스페이스를 생성한다고 볼 수 있습니다.

반면에 외부모듈은 다른 module-loader에 의존하여 모듈화하는 방법입니다.

External Module에는 ES Module, CommonJS(Node), Require.js(AMD)와 같이 따로

module-loader를 사용하는 모듈방법이 해당됩니다.

TS는 파일의 top-level(아무것으로도 감싸지지 않은 최상위 레벨)에 export가 존재하면 해당 ts파일을 모듈 파일로 생각합니다.

파일의 top-level에 아무런 import나 export가 존재하지 않는다면

TS는 파일을 모듈이 아닌 스크립트 파일로 생각하고 이는 일반적인 JS파일과 같이 파일 내에 생성된 변수는 window, global과 같은 전역 스코프에 영향을 미치게 됩니다.

그래서 내부와 외부 모듈의 차이점은?

외부모듈은 CommonJS/Require.js/ES Module과 같은 module-loaer에 의존성을 가지게 됩니다.

내부 모듈은 TS파일을 컴파일하고 난 후의 JS파일로 의존성을 해소합니다.

/* 컴파일 된 js, ESModule에 의존하고 있는 것을 볼 수 있다.*/ ➝ 모듈로더를 이용하는 외부 모듈
import * as express from "express";




/* namespace를 참조하는 TS 파일 */
/// <reference path="Validation.ts" />
/// <reference path="LettersOnlyValidator.ts" />
/// <reference path="ZipCodeValidator.ts" /
<!-- something.html 컴파일된 Javascript에 의존하고 있음 -->
<script src="Validation.js" type="text/javascript" />
<script src="LettersOnlyValidator.js" type="text/javascript" />
<script src="ZipCodeValidator.js" type="text/javascript" />
<script src="Test.js" type="text/javascript" />

Export

export 키워드를 사용하면, 선언된 모든 식별자

(변수, 함수, 클래스, 타입, 인터페이스 등)를 export 할 수 있습니다.

export interface Items {
  name: string
  info: string
  value: number
}

또한 export대상의 이름을 as 를 사용하여 이름을 변경rename할 수 있습니다.

export { Items as superItem };

import

import 키워드를 사용하여 export된 모듈을 로드 할 수 있습니다.

import { Items } from "../Item/Item";

그리고 마찬가지로 이름을 변경rename하여 가져올 수도 있습니다.

import { Items as superItem} from "../Item/Item";

require vs import (CommonJs와 ES6)

기본적으로 require와 import는 모듈 키워드입니다. 외부 파일이나 라이브러리를 불러올 때 사용합니다. 

require는 NodeJS에서 사용되고 있는 CommonJS 키워드이고, 

import는 ES2015에서 새롭게 도입된 키워드입니다. 

둘다 다른 파일의 코드, export 된 모듈을 불러온다는 같은 목적을 가지고 있지만, 다른 문법 구조 지니고 있습니다. 

const library = require("library")
import { library } from "library"

최근 ES6(ES2015) 모듈 시스템인 import가 많이 사용되고 있지만,

아직까지는 import 키워드가 100% 대체되어 사용될 수 없습니다.

<script> 태그를 사용하는 브라우저 환경과,

NodeJS에서도 CommonJS를 기본 모듈 시스템으로 채택하고 있기 때문에, 

Babel과 같은 ES6 코드를 변환(transpile)해주는 도구를 사용할 수 없는 경우에는

require 키워드를 사용해야 합니다.

즉 두 키워드 모두 다른 파일의 코드를 불러온다는 동일한 목적을 가지고 있지만 

다른 문법구조를 가지고 있고,

Babel과 같은 ES6 코드를 변환해주는 도구 없이는 require 키워드를 사용해야 합니다.

정리하자면 다음과 같습니다.

  • require()는 CommonJS를 사용하는node.js문이지만import()는 ES6에서만 사용됩니다.
  • require()는 파일 (어휘가 아님)에 들어있는 곳에 남아 있으며import()는 항상 맨 위로 이동합니다.
  • require()는 프로그램의 어느 지점에서나 호출 할 수 있지만import()는 파일의 시작 부분에서만 실행할 수 있습니다.
  • 일반적으로import()는 사용자가 필요한 모듈 부분 만 선택하고 로드 할 수 있기 때문에 더 선호됩니다.

import 는 require()보다 성능이 우수하며 메모리를 절약합니다.

주의점

모듈은 자신만의 스코프를 가지고 있으며, 오직 export된 모듈만 외부에서 접근 할 수 있습니다.

즉, namespce는 모듈에서 의미가 없습니다.

namespace는 전역 스코프에서 이름 충돌의 위험이 있는 식별자를 계층적으로 구분하기 위해 사용되나,

모듈은 경로와 파일 이름을 resolve하여 사용되므로 이미 파일 시스템에 의해 계층이 구분되어 있습니다.

export namespace Shapes { //최상위 모듈 shapes는 아무의미없이 triagle과 square를 감싸고있다.
    export class Triangle { /* ... */ }
    export class Square { /* ... */ }
}
/////GOOD
export class Triangle { /* ... */ }
export class Square { /* ... */ }

12) TS + OOP

객체지향이란 말은 이젠 생소함 보단 익숙한 단어일것입니다.

객제지향 프로그래밍은

서로 관련 있는 데이터와 함수를 객체로 정의해서 서로 상호작용할 수 있도록 프로그래밍하는 것입니다.

절자치향 프로그래밍의 경우 데이터를 받아오고 처리하는 방법으로 프로그래밍 되었다면

객제지향의 경우 프로그램을 '객체' 단위로 나누어 정의하고 각 객체들 간의 상호작용을 통해 프로그래밍을 합니다.

레고 부품(=객체)을 조립하여 레고 완성품(=프로그램)을 만드는 것이라 생각하면 조금 이해가 편하실겁니다.

타입스크립트를 이용하면, 자바스크립트도 함수형 프로그래밍을 더 명시적이고 쉽게 구현할 수 있습니다.

혹은 마치 Java를 JS로 컴파일하는 느낌이 드실수도 있으실겁니다.

당연한 장점은...

객체지향 프로그래밍은 코드 재사용을 통해 생산성을 높이고, 유지보수에 용이하다는 장점이 있습니다.

똑같은 바퀴가 4개인 자동차를 만든다고 가정할 때,

차 본체에 맞는 타이어 하나를 고르면 나머지 3개는 앞서 고른 타이어 하나와 똑같은 타이어로 골라 조립하면 되고,

또, 완성된 자동차에서 라이트 부분이 고장 나는 경우 그 부분만 다시 교체하면 됩니다.

객체 지향의 요소를 살펴보며 해당 부분에 대해 더 자세히 알아보겟습니다.

당연히 단점은...

객체지향 프로그래밍의 단점은 개발자 입장에서 

코딩의 난이도가 상승하고 -> 개발 속도가 느려진다는 단점이 있습니다.

객체지향 프로그래밍을 하기 위해선 높은 이해도가 필요하고

설계 단계에서부터 시간이 많이 소요되기 때문입니다.

그럼 타입스크립트엔 OOP(객체지향 프로그래밍)을 위한 요소로 어떤게 존재하는지 알아보겠습니다.

1. 캡슐화

서로 연관된 데이터와 함수를 캡슐화 즉, 하나의 객체로 묶어 두는 것을 뜻합니다.

예를 들어 사람라는 객체가 있으면

이 객체는 이름, 나이라는 데이터를 가질 수 있고 먹다, 일하다는 함수를 가질 수 있습니다.

class Person{
        public name: string;
        public age:  number;

        eat(): string{
            return `이름은 ${this.name}. 나이는 ${this.age}. 지금 밥을 먹고있다`
        }
        
        work(): string{
            return `이름은 ${this.name}. 나이는 ${this.age}. 지금 일을 하고있다`
        }

       constructor(name:string, age: number){
                  this.name = name;
                  this.age = age;
              }
    }

    const person = new Person('yelee', 20);

    console.log(person); // Person { name: 'yelee',

클래스에 name, age라는 맴버 변수와, eat,work라는 함수를 가지고있습니다.

클래스 안에 멤버 변수에 접근하기 위해선 this. 를 통해 접근해야합니다.

클래스에서 인스턴스를 생성하기위해선 new 키워드를 통해 생성하고,

각 멤버와 함수는 생성된 인스턴스를 통해 접근합니다.

인스턴스를 생성하기 위해선 생성자 함수인 constructor를 사용합니다.

접근제어자(public, private, protect)를 통해 외부에서 객체 내부의 특정 부분을 보이지 않게 할 수 있습니다.

class Person{
        private name: string;
        private age:  number;
       constructor(name:string, age: number){
                  this.name = name;
                  this.age = age;
              }
    }

    const person = new Person('yelee', 20);

    console.log(person.name); // PRIVATE!!!

위처럼 변수를 private로 선언햇을 경우엔 별도의 함수를 통해,

private 변수에 접근하여 특정값을 처리할 수 있습니다.

이를 도와주는 용도로 게터/세터를 사용할 수 있습니다

....
get GetName(): string{
  return this.name;
}
set SetName(name: string){
  this.name = name
} 

2. 인터페이스와 함수를 구현

class Car implements ICar {
    public color: string;
    public speed: number;

	UpSpeed(speed: number): number {
		this.speed += speed;
		return this.speed;
	};

	Driving() : void {
		console.log('운전중..');
	};
    constructor(private color: string, private speed: number) {
  		this.color = color;
  		this.speed = speed;
  	};
};

interface ICar {
	UpSpeed(speed: number): number;
}




const car = new Car('blue', 100);
console.log(car.UpS

ICar라는 인터페이스는 number형의 데이터를 받아

그 결과로 number형을 반환하는 ICar 인터페이스의 UpSpeed함수를 만들어야 한다고 정의했습니다.

Car클래스에서는 implements ICar를 통해서 UpSpeed 함수의 실체를 구현하고 있습니다.

클래스 선언문의 implements 뒤에 인터페이스를 선언하면 해당 클래스는 지정된 인터페이스를 반드시 구현하여야 한다. 이는 인터페이스를 구현하는 클래스의 일관성을 유지할 수 있는 장점을 갖는다.
인터페이스는 프로퍼티와 메소드를 가질 수 있다는 점에서 클래스와 유사하나 직접 인스턴스를 생성할 수는 없다.

3. 추상화

인터페이스와 비슷한 개념으로 클래스를 상속받아 다른 클래스를 구현할때 부모클래스의 함수를 재정의하도록 강제하는 경우가 있습니다. 이런 클래스를 추상클래스라고 합니다.

abstract class Car implements ICar {
	constructor(private color: string, private speed: number) {
		this.color = color;
		this.speed = speed;
	};


	UpSpeed(speed: number): number {
		this.speed += speed;


		return this.speed;
	};


	protected abstract Driving() : 

class앞에 abstract 를 붙여 정의하는데, 이 클래스는 자체로 인스턴스생성이 불가능합니다.

그리고 클래스 내부를 보면 Driving()이라는 함수가 있는데

인터페이스처럼 함수의 실체는 없고 그냥 이름과 매개변수, 반환형식만 정의되어 있습니다.

이제 Car를 상속받는 클래스는 Driving() 함수를 자체적으로 만들어야 합니다.

class Truck extends Car {
	private weight = 0;

	constructor(weight: number, color: string, speed: number,) {
		super(color, weight - speed);
		this.weight = weight;
	};




	Driving() : void { // 부모 클래스에서 정의한 driving을 정의해야함.
		console.log('짐을 싣고 운전중...');
	}

4. 상속

클래스의 상속은 extends를 사용합니다.

private을 제외하고 대부분의 부모요소를 상속받게 됩니다.

class Truck extends Car {

}




const truck: Truck = new Truck('blue', 100);
console.log(truck.UpSpeed(1

또한 super를 통해 부모클래스의 함수를 호출하거나,

class Truck extends Car {
	Driving() : void {
		super.Driving();
		console.log('짐을 싣고 운전중...');
	};
}

생성자 함수를 통해 만들때도 부모의 속성을 가져와 생성 할 수 있습니다.

export class Units { //유닛
  public name: string; //이름
  public maxHp: number; // 최대 체력
  public curHp: number; // 현재 체력
  public attack: number; // 공격력
  public speed: number; // 스피드
  public exp: number; // 경험치
  public turn: number; // 현재 턴
  public level: number; // 레벨
  public unitActive: boolean; // 사망여부
  
  constructor(name: string, maxHp: number, attack: number, speed: number, exp: number, turn: number, level: number, unitActive:boolean ){
    this.name = name;
    this.maxHp = maxHp;
    this.curHp = maxHp;
    this.attack = attack;
    this.speed = speed;
    this.exp = exp;
    this.turn = turn;
    this.level = level;
    this.unitActive = unitActive;
  }
}
//////////




export class Boss extends Units{
  public Boss: boolean
  public shield: number
  constructor(name: string, maxHp: number, attack: number, speed: number, exp: number, turn: number, level: number, unitActive: boolean) {
    super(name, maxHp, attack, speed, exp, turn, level, unitActive); //super로 부모것 사용
    this.Boss = true
    this.shield = 0
  

5. 다형성

위에서 처럼 인터페이스를 사용하거나 하나의 클래스에서 여러 자식클래스가 생성되었을때 자식 클래스는 구현해야할 함수를 가지고 있는 인터페이스가 되거나 부모클래스가 될 수 있습니다.

이처럼 하나의 어떤 형태에서 다양한 다른 형태가 만들어 질 수 있다고 하여 이를 '다형성'이라고 합니다.

profile
뜨겁고 매콤하고 화끈하게

0개의 댓글