JavaScript & TypeScript Essential -상속과 믹스인

김명성·2022년 4월 12일
코드베이스가 점점 커지다 보면, 중복된 코드는 계속 발견된다. 하지만 단순한 함수로 묶어서 온전히 제거하기는 어렵다. 코드를 작성함에 있어서도 그 목적에 부합되도록 분류하고, 그 분류 내에서 보다 빠르게 분류할 수 있게 여러가지 기능을 제공한다.
상속의 메커니즘 : 상속과 믹스인

클래스 생성을 통한 함수의 상속

클래스 생성

class Api {
  url: string;
  ajax: XMLHttpRequest;

  constructor(url:string){
    this.url = url;
    this.ajax = new XMLHttpRequest();
  }
// protected를 사용하므로써 class 밖에서 인스턴스 객체로 등장하지 않게 한다.
  protected getRequest<AjaxResponse>():AjaxResponse{
    this.ajax.open('GET', this.url, false);
    this.ajax.send();

    return JSON.parse(this.ajax.response);
  }
}

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

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

클래스 호출


// 인스턴스 생성시 매개변수 전달
  const NewsFeeds = new NewsFeedApi(NEWS_URL)
  newsFeed = store.feeds = makeFeeds(NewsFeeds.getData());

// 인스턴스 생성시 매개변수 전달
  const NewsFeeds = new NewsDetailApi(CONTENT_URL.replace('@id', id))
  const newsContent = NewsFeeds.getData()

기존의 단순 함수였던 코드를 클래스형 형태로 바꿀 때, 훨씬 더 코드가 무거워지는 경우가 있다.
특히나 작성한 코드가 하는 일이 작을때 이런 현상이 발생한다.
하지만 함수가 해야하는 일이 많아질수록, 함수 내에 작성해야 하는 코드가 많아질수록 점점 복잡도는 증가할 것이다.

함수는 구조가 없다. 단순히 입력된 statement를 읽고 실행할 뿐이다.
클래스는 구조라는 것을 갖게 된다. 즉 틀이라는 형식을 갖게 되는 것이다.
클래스는 만들어지는 목적이 있기 때문에 구조를 갖추는 것이며 나중에 기능이 추가될 때에도 초기의 복잡도는 유지되는 장점이 있다.

좋아진 것 같지 않고 나빠진 것 같다고 생각했을 때, 코드가 확장되었을 때를 생각하자.


믹스인 (mixin)

class를 함수 혹은 단독의 객체처럼 바라보면서 필요한 경우마다 class를 합성해서 새로운 기능으로 확장해 나가는 기법이다. 믹스인은 class를 독립적인 형태를 취급하며(종속이 없는) class간 상위,하위 관계가 묘사되지 않는다.
class Api {
  //constructor를 삭제했다.
    // constructor(){ }  
  // 생성자가 없으니 getRequest가 직접 url을 받는다.
  getRequest<AjaxResponse>(url: string): AjaxResponse {
    // 인스턴스를 생성하지 않으니 this를 삭제한다.
    const ajax = new XMLHttpRequest();
    ajax.open('GET', url, false);
    ajax.send();

    return JSON.parse(ajax.response);
  }
}
class Api {
  
  getRequest<AjaxResponse>(url: string): AjaxResponse {
    const ajax = new XMLHttpRequest();
    ajax.open('GET', url, false);
    ajax.send();
    return JSON.parse(ajax.response);
  }
}
API class는 메소드(getRequest)로 구현되어 있지만 현재 class Api는 마치 하나의 단독함수처럼 보인다.
// extends를 삭제한다.
class NewsFeedApi {
  getData(): INewsFeed[] {
    return this.getRequest<INewsFeed[]>();
  }
}
// extends를 삭제한다
class NewsDetailApi {
  getData(): INewsDetail {
    return this.getRequest<INewsDetail>();
  }
}
class NewsFeedApi {
  getData(): INewsFeed[] {
    return this.getRequest<INewsFeed[]>(NEWS_URL);
  }
}

class NewsDetailApi {
  //CONTENT_URL은 단독으로 사용될 수 없으니 id 값을 가져온다.
  getData(id): INewsDetail {
    return this.getRequest<INewsDetail>(CONTENT_URL.replace('@id', id));
  }
}
상위 클래스가 없는 NewsFeedApi와 NewsDetailApi 클래스에 this를 남겨두는것에 의문이 들 수 있다. 이 부분을 믹스인이라는 테크닉을 통해서 상속과 동일한 효과를 내도록 만들어 줄 수 있다.
function applyApiMixins(targetClass:any,bassClasses:any[]):void{
  bassClasses.forEach(bassClass => {
    Object.getOwnPropertyNames(bassClass.prototype).forEach(name => {
      const descriptor = Object.getOwnPropertyDescriptor(bassClass.prototype, name);

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

}
//typescript는 class의 합성까지는 예측하지 못하기에
// interface를 통해 class가 합성될 것이라고 알려준다.
interface NewsFeedApi extends Api {};
interface NewsDetailApi extends Api {};

applyApiMixins(NewsFeedApi,[Api]);
applyApiMixins(NewsDetailApi,[Api]);

믹스인 기법을 사용하는 이유

1. 유연성

  • 기존의 class extends 방식의 상속 방법은 코드에 적시 되어야 하는 상속 방법이다.
  • 상속의 관계를 바꾸고 싶다면 코드 자체를 변경해야 하기에 관계를 유연하게 가져갈 수 없다.

2. 다중 상속 받는 클래스 생성

  • class extends는 다중 상속을 지원하지 않기에 상위 클래스가
    여러 개인 클래스를 만들 수 없다.
  • 믹스인을 통해 하나의 클래스가 여러 개의 클래스를 상속 받을 수 있게 한다.

0개의 댓글