오늘은 작동 위임에 대해서 정리해 보려고 한다. 이 책 Part 1의 마지막 챕터이다.
지난 프로토타입 에 대한 포스팅에서 프로토타입이 어떠한 역할을 하는지에 대해서 자세하게 알아보았다. 한 마디로 요약을 해 보면 [[Prototype]] 체계는 한 객체가 다른 객체를 참조하기 위한 내부 링크이며, 엔진은 이 링크를 따라 연결된 객체에 특정 프로퍼티/메서드가 있는지를 체크한다. 이와 같이 자바스크립트의 무한한 가능성을 이끌어 낼 가장 중요한 핵심 기능이면서 실질적인 체계는 전적으로 '객체를 다른 객체와 연결하는 것'에서 비롯된다.
위임 지향 디자인
[[Prototype]]의 사용방법을 쉽게 이해하기 위해서는 [[Prototype]]이 클래스와 근본적으로 다른 디자인 패턴이라는 사실을 인지해야 하며, 기존 클래스/상속 디자인 패턴에서 작동 위임 디자인 패턴으로 사고방식을 바꾸려고 노력해야 한다는 점이다.
클래스 기반의 디자인 설계는 간단하게 설명하면 다음과 같다. 우선 가장 일반적인 부모 클래스(Task)와 유사한 태스크의 공통 작동을 정의하는 것부터 착수한다. 그리고 나서 Task를 상속받은 자식 클래스를 정의한 후, 이들에 특화된 작동은 두 클래스에 각각 추가한다.
클래스 디자인 패턴에서는 상속의 진가를 발휘하기 위해 가능 하면 메서드를 오버라이드(다형성) 할 것을 권장하고, 작동 추가뿐 아니라 때에 따라서 오버라이드 이전 원본 메서드를 super 키워드로 호출할 수 있게 지원한다. 공통 요소는 추상화하여 부모 클래스의 일반 메서드로 구현하고 자식 클래스는 이를 더 세분화(오버라이드)하여 쓴다. 클래스는 인스턴스화 하여 모든 요소를 옮겨서 사용한다.
이번에는 작동 위임을 이용해서 생각해 보자. 먼저 Task 객체를 정의하는데, 이 객체에는 다양한 태스크에서 사용할 유틸리티 메서드가 포함된 구체적인 작동이 기술된다. 태스크별 객체를 정의하여 고유한 데이터와 작동을 정의하고 Task 유틸리티 객체에 연결해 필요할 때 특정 태스크 객체가 Task에 작동을 위임하도록 작성한다.
자바스크립트의 [[Prototype]] 체계는 객체를 다른 객체에 연결한다. 클래스 같은 추상화 체계는 없다. 이러한 객체간 연결 스타일 코드의 특징은 다음과 같다.
일반적으로 [[Prototype]] 위임 시 상태값은 위임하는 쪽에 두고 위임받는 쪽에는 두지 않는다.
클래스 디자인 패턴에서 같은 메서드 이름을 사용하여 오버라이드를 했다면, 작동 위임 패턴에서는 서로 다른 수준의 [[Prototype]] 연쇄에서 같은 명칭이 겹칠 수 있기 때문에 가능하면 같은 이름을 사용하는 것은 피해야 한다.
작동 위임(behavior delegation)은 찾으려는 프로퍼티/메서드 레퍼런스가 객체에 없으면 다른 객체로 수색 작업을 위임하는 것을 의미한다. 작동 위임은 정말 강력한 디자인 패턴으로, 부모/자식 클래스, 상속, 다형성 등의 특징과는 완전히 구별된다. 또한 자바스크립트 엔진에서는 복수의 객체가 양방향으로 상호 위임하는 것은 허용하지 않는다.
멘탈 모델(Mental model)
방금 설명한 '클래스' 패턴과 '위임' 패턴의 차이점을 생각하면서 각각의 스타일로 작성된 코드를 살펴보자.
먼저 객체 지향적인 코드이다.
자식 클래스 Bar는 부모 클래스 Foo를 상속한 뒤 b1과 b2로 인스턴스화한다. 그 결과 b1은 Bar.prototype으로 위임되며 이는 다시 Foo.prototype으로 위임된다.
아래는 클래스 스타일 코드의 멘탈 모델을 개체와 관계만 표시해서 간략하게 도식화 한 것이다.
같은 기능을 하는 객체간 연결 스타일 코드이다.
앞에서 [b1 -> Bar.prototype -> Foo.prototype] 방향으로 위임한 것 처럼 여기서도 [b1 -> Bar -> Foo]로 [[Prototype]] 위임을 활용하며, 세 객체는 서로 단단히 연결되어 있다. 여기서 중요한 건 잡다하지 않고 단순하게 정리가 되었다는 점이다.
아래는 객체간 연결 스타일 코드의 멘탈 모델이다.
인트로스펙션(introspection)
타입 인트로스펙션은 인스턴스를 조사해 객체 유형을 거꾸로 유추하는 방식이다. 클래스 인스턴스에서 타입 인트로스펙션은 주로 인스턴스가 생성된 소스 객체의 구조와 기능을 추론하는 용도로 쓰인다. 아래는 instanceof 연산자로 객체 a1의 기능을 추론하는 코드이다.
Foo.prototype은 a1의 [[Prototype]] 연쇄에 존재하므로 instanceof 연산자는 마치 a1이 Foo 클래스의 인스턴스인 것 같은 결과를 낸다. 그래서 a1은 Foo 클래스의 기능을 가진 객체라고 쉽게 단정하게 된다. 물론 애당초 Foo 클래스 같은 건 없었고 Foo는 일반 객체에 불과하며, Foo가 참조한 임의의 객체(Foo.prototype)가 우연히 a1에 위임 연결되었을 뿐이다. 구문만 보면 instanceof가 a1과 Foo의 관계를 조사하는 듯 보이지만 실제로는 a1과 (임의로 참조하게 된) Foo.prototype 사이의 관계를 알려주는 일을 한다.
앞서 살펴 보았던 Foo/Bar/b1 예제에 instanceof 연산자와 .prototype을 이용하여 타입 인트로스펙션을 실시하면 다음과 같이 여러가지를 체크해 볼 수 있다.
'덕 타이핑(duck typing)' 이라고 하여 많은 개발자가 instanceof보다 선호하는 또 다른 타입 인트로스펙션 방법도 있다. 예를 들면, 위임 가능한 something() 함수를 가진 객체와 a1의 관계를 애써 조사하는 대신 a1.something을 테스트 해보고 여길 통과하면(a1에 직속된 메서드인지, 다른 객체에 위임되어 발견된 메서드인지 상관없이) a1은 .something()을 호출할 자격이 있다고 간주하는 것이다.
if (a1.something) {
a1.something();
}
이러한 가정 자체는 리스크가 없지만, '덕 타이핑'은 원래 테스트 결과 이외에 객체의 다른 기능까지 확장하여 추정하는 경향이 있어 리스크가 더해진다.(디자인적으로 취약함) 가장 유명한 덕 타이핑의 실체가 ES6 Promise이다. Promise에 대해서는 이후 포스팅에서 더 자세하게 다룰 예정이다.
모든 객체가 [[Prototype]] 위임을 통해 전달된 앞의 객체간 연결 방식에서는 다음과 같이 꽤 단순한 형태로 타입 인트로스펙션을 할 수 있다.
정리하기
작동 위임 패턴은 객체를 부모/자식 클래스 관계가 아닌 동등한 입장에서 서로 위임하는 형태로 연결되어 있다. 자바스크립트 [[Prototype]]은 태생적으로 작동 위임 체계다. 즉, 자바스크립트에 억지로 클래스 체계를 얹어 코드를 구현하느라 분투할지, 위임 체계로 [[Prototype]] 본연의 기능을 수용하여 활용할지는 얼마든지 선택할 수 있는 문제다.
객체만으로 구성된 코드를 구성한다면 사용 구문도 단순해질 뿐더러 실제로 코드 아키텍처 또한 간단하게 가져갈 수 있다. 객체를 다른 객체에 연결하는 디자인 패턴은 클래스라는 추상화 장치 없이도 직접 객체를 생성 및 연계한다. 이는 [[Prototype]] 기반의 작동 위임을 아주 자연스럽게 구성한다.
참고자료
- <YOU DON'T KNOW JS (this와 객체 프로토타입, 비동기와 성능)> 카일 심슨 저
'Prog. Langs & Tools > JavaScript' 카테고리의 다른 글
JS #8. 자바스크립트 Array 관련 메서드 총 정리 (map, filter, reduce, find, sort, find) (0) | 2020.05.26 |
---|---|
JS #7. 비동기성(Asynchrony): 지금과 나중(now and later) (1) | 2020.05.19 |
JS #5. 자바스크립트 프로토타입(Prototype) (0) | 2020.04.27 |
JS #4. 자바스크립트 클래스(Class) (0) | 2020.04.22 |
JS #3. 자바스크립트 객체(Object) (0) | 2020.04.15 |