타입스크립트에서의 상속

Dengo·2022년 5월 6일
0

TypeScript

목록 보기
2/2
post-thumbnail

상속을 간단하게 표현하면 "공통된 요소를 묶는다." 라고 쉽게 이해 할 수 있다.
상속을 구현하는 방법으로 클래스와 믹스인이 있는데 이번에도 하나씩 알아보자.

Class를 이용한 상속 구현

일단 API를 사용하는 부분과 관련한 클래스를 하나 만들어보자.

class Api {
  url : string;
  ajax : XMLHttpRequest;

  constructor(url : string) {
  	// 생성자
    this.url = url;
    this.ajax = new XMLHttpRequest();
  }

  protected getRequest<AjaxResponseType>() : AjaxResponseType {
  	// Generic을 이용해서 입력 타입과 반환 타입을 같게한 API 호출 함수.
    this.ajax.open('GET', this.url, false);
    this.ajax.send();

    // 경우에 따라서 반환하는 값이 NewsFeed[]일때도, NewsDetail일때도 있는 상황
   	// => Generic으로 해결한 것
    return JSON.parse(this.ajax.response);
  }
}

API와 관련한 공통부분을 작성한 클래스이다.
이것을 상속해서 NewsFeed를 받는 API와 NewsDetail을 받는 API를 각각 작성해보자.

class NewsFeedApi extends Api {
  getData() : NewsFeed[] {
    return this.getRequest<NewsFeed[]>();
  }
}

class NewsDetailApi extends Api {
  getData() : NewsDetail {
    return this.getRequest<NewsDetail>();
  }
}

사용할때는 당연히 만들어준 NewsFeedApi 클래스 혹은 NewsDeatilApi 클래스를 사용해서 객체를 만들어내면 된다.

const api = new NewsFeedApi("Url입력");
api.getData();

이런식으로 하면 된다.

믹스인을 사용한 상속 구현

개인적으로는 이게 좀 이해하기 어렵다.
사실 위의 클래스를 이용한 방법이라면 기존에 썼던 방법이기도 하고 직관적이어서 쉬운데
믹스인 방법은 내게는 좀 생소한 방식이었다.

일단 역시 왜 이걸써? 라는 질문을 던져봐야 한다.
클래스도 있잖아! 라고 생각할 수 있지 않나

믹스인 방법을 사용하게 되면 클래스를 하나의 독립적인 객체로 바라보고 이들간에 굳이 상속을 하네 마네와 같은 정보를 적지 않는다.
그냥 각각을 선언해두고 "믹스인" 이라는 방법을 사용해서 합성한다.
일단 의미적으로 이해하면 이렇다.
가장 결정적으로 이런 방식을 굳이 왜 채택하게 되는 걸까?
바로 클래스의 다중상속을 구현하기 위함이다.

프로젝트의 규모가 겁나 크다고 상상을 해보자.
그리고 인기가 많아서 업데이트도 잦아 삭제되는 기능과 업데이트 되는 기능이 많은 상황이다.
보통은 이런 경우에 이제 코드의 설계를 어떻게 해놓느냐에 따라서 이런 수정에 대해 유연하게 대처를 할 수 있는데 지금 우리가 보고 있는 이 다중 상속이 이런 상황에서의 중요한 열쇠로 자리매김 할 수 있다.
따라서 믹스인은 이렇게 대규모의 프로젝트를 만드는 경우가 아니면 오히려 직관적이지 않다고도 표현 할 수 있겠다.

구현은 어떻게 할 수 있을까?

class Api {
  getRequest<AjaxResponseType>(url : string) : AjaxResponseType {
    const ajax = new XMLHttpRequest(); // 생성자가 없어졌으니까 이렇게 따로 걍 만듬
    ajax.open('GET', url, false);
    ajax.send();

    // 경우에 따라서 반환하는 값이 NewsFeed[]일때도, NewsDetail일때도 있는 상황
    return JSON.parse(ajax.response);
  }
}

class NewsFeedApi {
  getData() : NewsFeed[] {
    return this.getRequest<NewsFeed[]>("Url 입력");
  }
}

class NewsDetailApi {
  getData(id : string) : NewsDetail {
    return this.getRequest<NewsDetail>("Url 입력");
  }
}

위에 설명한대로 Api클래스를 상속받았던 두 클래스를 그냥 독립적으로 만들어 버렸다.
constructer를 지운것도 독립적인 주체로 바라보기 위함인듯 하다.
어쨌든 이렇게 독립적으로 만들었으니 이제 이것들을 합칠수 있는 기능이 필요하다.

function applyApiMixins(targetClass : any, baseClasses : any[]) {
  baseClasses.forEach(baseClass => {
    Object.getOwnPropertyNames(baseClass.prototype).forEach(name => {
      const descriptor = Object.getOwnPropertyDescriptor(baseClass.prototype, name);

      if (descriptor) {
        Object.defineProperty(targetClass.prototype, name, descriptor);
      }
    });
  });
}

interface NewsFeedApi extends Api {}; // TypeScript 컴파일러에게 이 두가지가 합성될것임을 알려주는 코드
interface NewsDetailApi extends Api {};
applyApiMixins(NewsFeedApi, [Api]);
applyApiMixins(NewsDetailApi, [Api]);

applyApiMixins를 만들긴 했지만 내부적으로 정확히 어떻게 동작하는지 이해할 필요는 없다.
그냥 인자로 받은 baseClasses가 targetClass에 다중상속 되는 동작이 일어난다 정도로 이해하면 될듯하다.

그리고 마지막에 저렇게 함수를 호출해줘야 두 클래스가 합성 될 수 있다.

profile
Software Engineer (전산쟁이)

0개의 댓글