티스토리 뷰
데코레이터(Decorator) 패턴, 혹시 한 번씩은 들어보시거나 기억나시나요? 해당 패턴은 기존 객체의 기능을 동적으로 추가하거나 덮는 식으로 전체적인 기능에 변화를 주거나 하는데 널리 사용되는 패턴 중 하나입니다.
해당 패턴에 대해 더 자세히 알고 싶다면 https://mttankkeo.tistory.com/19 참고해주세요.
앞서 데코레이터 패턴을 언급한 이유는 바로 여러분에게 소개할 믹스인과 그 사용 목적이 매우 유사하기 때문입니다.
물론 데코레이터 패턴과 믹스인 개념에 이미 익숙한 분들도 계시겠지만, 믹스인은 주로 단일 상속만을 지원하는 Dart나 Javascript 같은 언어에서 사용되는 개념입니다. 다중 상속을 지원하는 C++ 같은 언어에서는 믹스인의 필요성이 크지 않습니다. 또 한 가지 차이점은 믹스인은 주로 런타임에 확장되는 개념이 아니라, 구현(선언) 단계에서 확장을 목표로 한다는 것입니다. 다시 말해, 런타임에서의 유연성은 데코레이터 패턴이 좀 더 뛰어나다고 볼 수 있습니다.
물론 믹스인의 유연성이 낮다고 해서 무조건 단점만 있는 것은 아닙니다. 데코레이터 패턴은 여러 겹으로 객체를 감싸는 경우 코드가 복잡해지고 가독성이 떨어질 수 있는 반면, 믹스인은 마치 레고 블록을 조립하듯 작고 독립적인 코드 조각들을 조합하듯 사용할 수 있기 때문에 코드 구조가 비교적 간결하고 후임 개발자 또는 기여자에게 코드의 의도가 명확하게 전달될 수 있다는 장점이 있습니다. (물론 이 부분은 개인적인 프로그래밍 스타일이나 프로젝트 특성에 따라 다르게 느껴질 수 있습니다.)
기존에 믹스인의 존재를 몰랐던 분들은 어때요? 믹스인에 조금은 흥미가 돋나요? 잔말 말고 바로 타입스크립트에서 믹스인을 구현하는 방법을 알아보도록 하겠습니다.
// 해당 타입은 주어진 제네릭 클래스의 생성자 함수를 정의합니다.
type Constructor<T extends {}> = new (...args: any) => T;
먼저 믹스인을 구현하기 이전에 타입스크립트에서 생성자 함수의 타입을 어떻게 나타낼 수 있는지에 대한 방법을 알아야 합니다.
new 키워드는 클래스의 생성자(constructor)을 의미하고 인자들은 생성자의 인자들을 의미합니다, 반환하는 타입은 생성자가 뱉는 인스턴스의 타입을 의미합니다.
function Mixin(Base: Constructor<{}>) {
return class Mixin extends Base {
print() {
console.log("hello world");
}
}
}
// 이러한 방식으로 선언하는 경우도 있습니다.
// function Mixin<T extends Constructor<{}>>(Base: T)
해당 코드의 경우 믹스인을 구현하는 예시들 중 하나입니다. Mixin 함수를 호출하면 주어진 생성자를 상속한 클래스를 반환하는 것을 볼 수 있습니다. 여기서 다른 언어를 사용하다 JavaScript를 접한 사람들이, 함수와 클래스가 모두 1급 객체로 다뤄진다는 사실에 놀라곤 합니다. 따라서 JavaScript에서는 함수와 클래스를 변수에 담거나, 다른 함수의 인자로 넘기거나, 함수의 반환값으로 사용할 수 있습니다.
그렇다면 특정 클래스만을 대상으로 확장하는 믹스인은 어떻게 구현할 수 있을까요?
class Example {
say() {
console.log("hello world 1")
}
}
function ExampleMixin(Base: Constructor<Example>) {
return class ExampleMixin extends Base {
say() {
super.say();
console.log("hello world 2");
}
}
}
해당 코드에서는 Example 클래스를 대상으로 확장하는 ExampleMixin을 볼 수 있습니다, 이전 코드 예시와 달리 Example의 생성자만을 인자로 받고 있으며 이로 인해서 생성자 타입은 Example 그 자체이거나 하위 타입으로 제한된다는 사실을 알 수 있습니다.
이러한 생성자 인자의 타입 선언을 통해서 ExampleMixin은 Example 클래스의 기존 인터페이스에 접근하고 수정할 수 있는 권한을 갖게 됩니다.
이번엔 해당 믹스인을 통해서 어떻게 클래스를 확장할 수 있는지 알아보도록 하겠습니다.
class ExpandedExample extends ExampleMixin(Example) {
...
}
해당 코드처럼 기존 클래스 상속과 유사한 방식으로 ExampleMixin을 사용할 수 있습니다. 즉, 확장하고자 하는 클래스를 ExampleMixin 함수로 감싸기만 하면 손쉽게 클래스를 확장할 수 있습니다.
const ExpandedMixin = ExampleMixin(Example);
물론 위와 같이 ExampleMixin(Example)을 새로운 변수(또는 상수)에 할당할 수도 있지만, 이 경우에는 활용도가 제한적이기 때문에 일반적으로는 저는 첫 번째 방식을 선호합니다.
솔직히 개인적으로 믹스인은 기존 클래스 확장 방식보다 유연하고 강력한 면모를 보여주는 기능이라고 생각합니다, 기존 단일 상속의 문제점을 쉽게 극복할 수 있으니까요.
특히 TypeScript의 제네릭과 결합하면 타입 안정성을 유지하면서도 다양한 클래스에 적용 가능한 확장 클래스를 마구 만들 수 있습니다. 앞으로 코드를 작성할 때 믹스인을 활용하여 더욱 유연하고 효율적인 코드를 작성해 보세요!
Happy coding in the another world!
'웹 (Web)' 카테고리의 다른 글
[Web API] Reflow를 유발하여 레이아웃을 제어하는 방법 (0) | 2024.06.19 |
---|---|
[JS] 자바스크립트에서 여러가지 유용한 기능들 (0) | 2024.06.01 |
[TS] 타입스크립트에서 여러가지 유용한 기능들 (0) | 2024.05.30 |
[JS] this 인스턴스를 참조할 수 없는 문제. (0) | 2024.03.23 |
[Webpack, JSP] 빌드된 내용을 HTML 문서에 모두 삽입하는 방법 (0) | 2024.03.19 |
- Total
- Today
- Yesterday
- 현재 오프셋
- canvas animation
- TypeScript
- JavaScript
- NestedScrollConnection
- 추상 팩토리
- touch slop
- Flutter
- android
- 타입스크립트
- 안드로이드
- 팩토리 메서드
- 안드로이드 개발
- npm package
- nested scrolling
- 객체 지향
- jetpack compose
- 최대 오프셋
- utility types
- 객체지향
- conditional types
- animatable-js
- 문자열 템플릿
- web
- 디자인 패턴
- 책임 연쇄
- Structural Pattern
- AutomaticKeepAliveClientMixin
- js animation
- Factory Method
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 |