타입과 인터페이스 | 상속과 믹스인

dev__bokyoung·2022년 9월 6일
0

해커뉴스 클론코딩

목록 보기
9/12
post-thumbnail

프롤로그

타입인터페이스의 차이는 아주 ~ 조금 ~ 차이가 난다고 한다. 코드는 일관성이 중요하기 때문에 어떤 미세한 기능 때문에 그것을 써야 하는 경우가 있다면 그 코드로 써야 겠지만 대부분은 취향의 차이라고 한다.

타입을 인터페이스로 바꿔보려고 한다.

1. 타입 알리아스와 인터페이스의 차이

타입을 결합시키거나 조합시키는 차이

인터페이스는 타입 알리아스의 유니언 (a | b) 타입을 지원하지 않는다.

인터페이스가 좀 더 코드를 읽는다는 느낌이 있다. (표현 기법의 차이)

2. 인터페이스로 변환

코드를 보자. 앞에 interface 를 바꾸고 = 을 없앴다.
그리고 확장할 때는 && 를 쓰는게 아니라 extends 를 써준다.

interface Store {
  currentPage: number;
  feeds: NewsFeed[];
}

interface News {
  readonly id: number;
  readonly time_ago: string;
  readonly title: string;
  readonly url: string;
  readonly user: string;
  readonly content: string;
}

interface NewsFeed extends News {
  readonly comments_count: number;
  readonly points: number;
  read?: boolean;
}

interface NewsDetail extends News {
  readonly comments: NewsComment[];
}

interface NewsComment extends News {
  readonly comments: NewsComment[];
  readonly level: number;
}

3. 상속과 믹스인

상속 이란 공통요소를 만들어 놓고, 공통 요소를 확장 할 수 있는 개별 요소를 만들게 되는 식. 이 관계를 상속이라고 한다.

상속을 다루는 메커니즘은 2가지가 있다.

  • 클래스
  • 믹스인

‣ 우선 API 코드를 class 로 다뤄보자

//class 를 만들고 한번 초기화의 과정을 해줘야 한다. constructor 가 초기화를 담당 
class Api {
  ajax: XMLHttpRequest;
  url: string;

  constructor(url: string) {
    this.ajax = new XMLHttpRequest();
    this.url = url;
  }

// api 를 요청하는 공통적인 부분
  getRequest<AjaxResponse>(): AjaxResponse {
    this.ajax.open('GET', this.url, false);
    this.ajax.send();

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

// 타입을 다르게 해서 가져오는 부분. api 클래스에서 상속을 받았다.
class NewsFeedApi extends Api {
  getData(): NewsFeed[] {
    return this.getRequest<NewsFeed[]>();
  }
}

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

class 는 목적을 위한 구조를 가지게 된다. 목적을 위한 구조를 가지게 되면 좋은 점은 코드가 나중에 더 많은 구조를 가지게 될 때 초기의 복잡도는 유지하면서 바깥쪽에서는 그 단순함을 꾸준히 유지 할 수 있다는 장점이 있다.

코드베이스가 작을 때는 변경된 코드가 너무 복잡해서 더 나빠진 것 같은데 라는 생각이 들 수 있지만 코드가 점점 커질 수록 코드의 장점이 극대화 될 것이다.

사용하는 쪽을 보자

function newsFeed(): void {
  //인스턴스 생성 필수 
  let api = new NewsFeedApi(NEWS_URL);
  let newsFeed: NewsFeed[] = store.feeds;
  const newsList: string[] = [];
  
 // .. 코드 중략 ... 

//api.getData() 로 url 을 넘겨줄필요도 없고, 타입을 지정할 필요도 없다. 
  if (newsFeed.length === 0) {
    newsFeed = store.feeds = makeFeeds(api.getData());
  }
  
  // .. 코드 중략 .. 

  

  template = template.replace('{{__news_feed__}}', newsList.join(''));
  template = template.replace('{{__prev_page__}}', String(store.currentPage > 1 ? store.currentPage - 1 : 1));
  template = template.replace('{{__next_page__}}', String(store.currentPage + 1));

  updateView(template);
}

믹스인 이라고 하는 기법은 class 를 사용해서 상속을 구현하지만 class의 extends라는 기법을 사용하지 않고 class 를 마치 함수처럼, 단독의 객체 처럼 바라보면서 필요한 class를 합성해서 새로운 기능으로 확장해나가는 기법이다. 믹스인은 class 자체를 훨씬 더 독립적인 주체로 바라본다.

굳이 extends 기능이 있는데 왜 쓰는가?
1. 기존의 extends 라고 하는 방식의 상속 방법은 코드에 적시 되어야 하는 상속 방법이다. 상속의 관계를 바꾸고 싶으면 코드 자체를 바꿔야 한다는 뜻 (관계를 유연하게 가져갈 수 없다.)
2. JS 와 TS 의 extends 문법은 다중 상속을 지원하지 않는다.(여러개의 상위 클래스를 상속받고 싶으면 불가능하다는 이야기)

‣ class 를 mixin 으로 바꿔보자

//targetClass 로 제공된 class 에다가 baseClass 들로 제공된 n 개의 클래스 기능들을 합성시키는 역할의 함수 

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

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

class Api {

  getRequest<AjaxResponse>(url: string): AjaxResponse {
    const ajax = new XMLHttpRequest();
    ajax.open('GET', url, false);
    ajax.send();

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

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

class NewsDetailApi {
  getData(id:string): NewsDetail {
    return this.getRequest<NewsDetail>(CONTENT_URL.replace('@id', id));
  }
}

// 타입스크립트 컴파일러한테 두개가 합성 될거라고 알려줘야 한다. 
interface NewsFeedApi extends Api {};
interface NewsDetailApi extends Api {};

// 믹스인 사용하는 쪽 
applyApiMixins(NewsFeedApi, [Api]);
applyApiMixins(NewsDetailApi, [Api]);

4. 이론) 클래스

클래스는 자바스크립트에서 객체를 만드는 가장 정교한 방법을 제공한다.
클래스는 아직 객체가 만들어 지지 않은 상태. 객체가 만들어 지면 어떻게 만들어지고 어떤 기능을 가질거야 라고 하는 일종의 설계도에 가깝다. 그래서 실제 객체로 아직 형상화 되어 있지는 않다.

그렇다면 실제객체로 형상화 되려면 어떻게 해야하는가?
인스턴스라고 하는 것을 만들어야 하고 그렇게 만들어진 인스턴스는 실제로 객체인데 클래스의 설계도대로 현실화 된 객체라고 하는 뜻이다.

  • static 이라는 것은 인스턴스 객체에 포함되지 않는 정말 정적으로 shpae class 에 연결되어 있는 속성이라는 뜻 (굳이 인스턴스 객체마다 데이터 혹은 메소드를 넣을 필요가 없는 경우, 정확히는 인스턴스들끼리 관계가 없이 shape class 라고하는 특정 클래스가 포함하고 있는 데이터나 메소드인 경우에 정적으로 만들면 효과적으로 사용하는 경우가 있다.)

  • 상속받은 하위클래스에서는 반드시 construcotor에 super()라고 써서 부모클래스를 초기화 시켜줘야한다.

  • readonly 는 인스턴스객체가 만들어진 이후에 인스턴스 객체 외부에서 이 name 자체의 값을 바꿀 수 없는 속성을 말한다.

  • 내부속성을 보호하기 위한 방법은 두가지가 있다. 하나는 private 속성을 사용하는 것. 보이지 않게 만들고 바깥쪽에서 사용하는 것은 getter 혹은 setter 라는 것을 사용해서 따로 제공하는 방법 | 또는 readonly

  • abstract는 추상 메소드이다. 추상 클래스일 경우에 사용하는 기능. 이 클래스를 상속받은 하위클래스 한테 반드시 실체적인 코드를 구현해라 하고 지시하는 역할. 그래서 추상클래스의 추상메소드가 있는 경우에는 반드시 상속받으면서 해당하는 메소드를 실체화된 코드로 구현해야 한다.

  • interface 를 클래스와 연결지으면 클래스의 설계를 제한하는 용도로 사용하게 된다. class MyContainer implements containers {...} 설계도로써 컨테이너라는 인터페이스를 사용할거야! 라고 하면 implements 의 키워드를 사용해야 한다.

  • public : 인스턴스객체 그대로 드러나서 사용할 수 있는 것

  • private : class 안에서만 통용되는 것 (상속받은 하위클래스 혹은 부모클래스에서도 접근이 안되는 방식)

  • protected : 외부에는 공개되지 않지만 내부에서는 자식 클래스 즉, 확장된 클래스 접근할 수 있는 요소 (private 보다 범위가 넓다)

인스턴스

구체적으로 현실화 된 객체를 인스턴스 라고 한다.

함수로 만드는 방법

암묵적인 방식이 존재

new 연산자를 함수 호출 앞에 쓰게 되면 암묵적인 매카니즘이 작동하게 됨

  1. 빈객체를 하나를 만듦 (인스턴스객체)
  2. CartV1 한테 그 빈 객체를 전달해줌 ( 그 빈객체를 가르키는 지시어가 this)
  3. 함수를 new 연산자로 만들고 났을 때 인스턴스 객체를 만들고 나서 함수가 종료되면, 자동으로 this 객체를 return 하게 되어 있다.
  4. 내부적으로는 또 암묵적 매카니즘이 작동하는데, 만들어진 빈 this 객체에 prototype 속성을 할당한다.

객체는 모두 __proto__라는 속성을 가지고 있고,
함수는 prototype이라는 속성을 기본적으로 가지고 있다.

클래스

직관적이고 하나로 묶여있어 보기 편하다.

에필로그

점점 고급문법이 나오면서 난이도가 높아져 간다. 클래스는 강의를 들으면서 많이 접했는데 완벽하게 이해가 가지 않았었다. 다시들으면서 이론도 같이 접하니까 이해가 확실히 간다. 함수에서 쓴 prototype 또한 마찬가지
깊게 듣고 이해하려고 하니 이해가 간다.

profile
개발하며 얻은 인사이트들을 공유합니다.

0개의 댓글