TypeScript vs Python Mixin 차이를 알아보자잇! (feat.다중상속)
안녕하세요! 개발자 Jay입니다~ 최근에 TypeScript 공부를 하고 있습니다!
회사 기술 스택 중 nest.js를 사용하고 있어서 + 새로운 언어에 대한 궁금증 때문인데요~
python을 오래 쓰면서 TypeScript 문법을 공부하다 보면 오 이거 문법이 많이 다르네, 특이하네 하는 것들이 있습니다.
그중 하나가 Mixin 문법인데요. 오늘은 TypeScript와 python의 Mixin에 대해서 알아보겠습니다.
1. Mixin(믹스인) 이란?
처음 믹스인을 봤을 때는 "이거 그냥 클래스(Class)아닌가?" 라고 생각했는데 비슷하면서도 용도가 약간 다릅니다.
믹스인은 클래스와 동일한 형태이지만 필요한 메서드(Method)만 확장하기 위해 쪼개져 있는 클래스라고 보면 될 것 같습니다.
그렇기 때문에 특정 메소들만 일부 구현되어 있고 여러군데서 확장하는 형태로 사용하고, 다중 상속으로 상속으로 여러개 믹스인을 상속받아서 사용하는 경우가 있습니다 (django 사용하시면 많이 보실거 같네염)
자 그럼 이제 코드로 한번 보겠습니다.
2. TypeScript vs Python Mixin
파이썬, 타입스크립트로 믹스인 예제를 한번 구현해봤습니다.
먼저 파이썬 코드를 보시면 믹스인은 특정 컨셉을 가진 메소들만 쪼개서 원할 때 확장하여 사용하는 클래스입니다. 장고(django)에서는 DRF 사용하다 보면 이런 믹스인들을 많이 볼 수 있죠!
자주 사용하는 언어다 보니 아주 익숙하고 편안합니다. 그럼 우측이미지 타입스크립트로 된 코드를 보실까요??
파이썬 믹스인과 비슷하죠? 근데 동작할까요?! ㅋㅋㅋ
사실 타입스크립트는 다중 상속을 지원하지 않습니다. 그럼 믹스인을 여러 개 사용 못하는 건가?!
아니죠! 타입 스크립트는 또 다른 방식으로 다중 상속을 하더라고요.
그리고 신기한 게 타입 스크립트에서 믹스인을 Funtion Factory형태로 만들더라고요. 그래서 사실 아까 위에서 본 형태는 정확히 믹스인의 형태라고 보기엔 힘들고 그냥 클래스의 상속 형태입니다. 다시 Funtion Factory 형태로 만들면 아래와 같습니다.
타입스크립트에서는 믹스인은 이렇게 구현한다고 하네요.
자 이제 이런 형태에서 다중 상속으로 믹스인 여러 개를 상속해보도록 해보겠습니다!
와우 뭔가 웅장하네요... 파이썬은 저런 거 필요 없는데ㅎㅎ
주석을 남겨두긴 했지만 간단히 단계별로 설명하자면 (예제 코드는 여기서 확인)
- 인터페이스로 확장할 믹스인을 선언
- 실제 다중 상속은 상속하는 클래스의 prototype 객체에 defineProperty로 추가
- 2번 과정이 forEach로 반복
이런 단계가 됩니다. 기본적으로 python문법과는 매우 다른 모습이고 사실 처음에 "왜 클래스의 상속에 함수 형태가 있지?" 라는 의아함을 가졌었고, nest.js 스터디할 때 "이게뭐지?" 라고 당황했던 기억이 나네요 ㅋㅋ
Robot클래스 prototype에 믹스인이 확장된 전, 후 디버깅을 보자면
보시면 fly, swim 메서드가 추가된 걸 확인할 수 있습니다. applyMixins를 구현하면서 사용한 함수들은 주석을 확인하시거나 여기서 보시면 될 것 같습니다.
3. 왜 TypeScript(JavaScript)는 다중상속(Multiple-Inheritance)를 지원하지 않을까?
그럼 우리는 이렇게 생각해볼 수 있습니다.
아니 이렇게 편하게 Python은 다중상속을 지원하는데 다른 언어들은 왜? 지원하지 않을까?
다 이유가 있을겁니다!
그 중 추측하는 바로는 다중상속을 지원할 경우 생기는 문제인 다이아몬드 문제(Diamond Problem)가 있습니다.
다이아몬드 문제란 B, C 클래스 두개를 다중상속 받을때 둘 다 run() 이라는 메소드를 가지고 있으면 상속받는 클래스는 B, C 클래스 둘 중 어떤 클래스의 run() 메소드를 사용해야하는지 모르게 됩니다.
그렇다면 언어마다 다중상속을 지원하고 지원하지 않는이유는 아마도 언어마다 철학이 다르고 지원하는 기능이 다르기 때문일 것입니다.
지원하지 않는 이유를 추측해본다면 아마도 이런 다이아몬든 문제를 개발자가 겪었을때 수정 혹은 고려하면서 개발을 하지 않게 하기 위함일 것이라 생각됩니다.
python의 경우 MRO(Method Resolution Order)라는 기준? 조건?을 통해 다이아몬드 문제를 해결하였습니다.
위 예제에서 C, D의 work() 메소드는 어떤 클래스의 메소드를 호출할까요?
C의 경우 A, D의 경우 B 클래스의 메소드를 호출했습니다. python의 경우 다중상속일 경우 같은 메소드가 존재하는경우 MRO를 통해서 순서를 정하게 됩니다.
python에서 __mro__() 메소드를 통해 각 클래스의 MRO를 확인할 수 있습니다.
우선순위는 왼쪽부터입니다. C, D 클래스의 MRO가 다른것을 확인할 수 있습니다. 다중상속에서 왼쪽부터 가장 높은 우선순위를 가진다고 보면 됩니다.
추가로 TypeScript의 Interface에서는 다중상속이 가능한데, 가능한 이유는 Interface의 경우 실제로 어떤 기능이나 로직을 작성하는 것이 아닌 프로퍼티와 메소드의 타입등을 정의하는 것이기 때문이다.
4. 정리
개인적으로 다중상속을 지원하는 python이 좀 더 편하게 느껴졌던 것 같습니다. 아마도 MRO라는 기준을 다른 언어에서 도입하지 않은 이유가 있을것 같기도 하고, 아니면 언어가 생긴 시대에 따라 도입여부가 달라졌을 것 같습니다. (혹은 각 언어의 철학)
다중상속 자체는 지원이 안되더라도 다른방식으로 다중상속을 구현하는것도 신선했으며, 언어를 알아가는데 새로운 재미였던 것 같습니다!
그럼 오늘도 즐거운 코딩하시길 바랍니다~
참고