As i wish

[You don't know JS] - 작동 위임 본문

JavaScript

[You don't know JS] - 작동 위임

어면태 2019. 6. 7. 00:06

You don't know JS에 PART 1에 마지막 장인 '작동 위임'에 대하여 포스팅해보겠습니다.

지금 까지 [[Prototype]]에 대하여 공부하였고 '클래스'나 '상속'의 맥락에서 [[Prototype]] 이 적절하지 않은 지 공부했습니다. 따라서 [[Prototype]] 은 자바스크립트에서 여타 다른 클래스 지향 언어들과 달리 한 객체가 다른 객체를 참조하기 위한 내부 링크라고 정의할 수 있죠.

또한 참조하는 객체에 존재하지 않는 프로퍼티/메서드를 참조하려고 할 경우 엔진은 [[Prototype]] 링크를 따라가며 연결된 객체마다 프로퍼티/메서드가 있는지 뒤져보고 발견되지 않으면 다음 [[Prototype]] 링크에 연결된 객체를 타고 이동하는데 이것을 '프로토타입 연쇄'라고도 정의하였습니다.

즉, 자바스크립트는 '객체를 다른 객체와 연결하는 것' 이 핵심 기능이자 무한한 가능성을 이끌어 낼 수 있다는 것이죠.

 

1. 위임 지향 디자인으로 가는 길

[[Prototype]] 의 사용방법은 클래스와 근본부터 다른 디자인 패턴이라는 점부터 인지 해야 합니다. 기존 클래스의 상속 디자인 패턴이 아닌 위임 디자인 패턴으로 사고방식을 바꾸어야 하죠.

1.1 클래스 이론

클래스 디자인 패턴에서 상속의 진가를 발휘하기 위해서는 메서드 오버라이드(다형성)를 권장하고 때에 따라서는 오버라이드 이전 메서드를 super를 통해 호출하기도 하죠.

class Task {
  id;
  Task(ID) {id = ID;}
  outputTask() {output(id);}
}

class XYZ inherits Task {
  label;
  XYZ(ID, Label) {super(ID); label = label};
  outputTask() {super(); output(label);}
}

class ABC inherits Task {
    //....
  }

위처럼 XYZ, ABC는 Task로부터 상속받아 동작하고 때에 따라 메서드를 오버라이드 하기도 합니다.

1.2 위임 이론

같은 코드를 작동 위임을 이용하여 보겠습니다. 

Task = {
  setId: function(ID) {this.id = ID},
  outputID: function() {console.log(this.id);}
}

XYZ = Object.create(Task);
XYZ.prepareTask = function(ID, Label) {
  this.setID(ID);
  this.label = Label;
}

XYZ.ouputTaskDetails = function() {
  this.outputID();
  consooe.log(this.label);
}

//ABC....

위처럼 클래스 지향과 대비하여 책에서는 이런 코드를 OLOO (Objects Linked to Other Object)라고 부릅니다. 또한 상속과 다른 점은 메서드를 오버라이드 하지 않는 점이 있죠. 만약 오버라이드를 했다면 앞 장에서 언급한 적이 있는 '메서드 가려짐' 현상이 발생하기 때문이죠. 또한 상속받지 않았기 때문에 별개의 객체로 존재하고 필요시에만 '작동 위임'에 따라 필요한 메서드를 찾아 동작하는 방식이죠.

위에 상속 패턴이 수직적이라면 위임 패턴은 수평적이라고 볼 수 있죠.

5장 2.1 가려짐

 

[You don't know JS] - 프로토타입

이번에는 5장인 프로토타입에 대하여 공부해 보겠습니다! 1. [[Prototype]] 명세에 따르면 자바스크립트 객체는 [[Prototype]] 이라는 내부 프로퍼티가 있고 다른 객체를 참조하는 단순 레퍼런스로 사용합니다. 드..

eomtttttt-develop.tistory.com

위처럼 '작동 위임'은 앞서 계속 언급했듯이 찾으려는 프로퍼티/메서드 레퍼런스가 객체에 없으면 다른 객체로 수색 작업을 위임하는 것을 의미하죠. 이는 기존 클래스의 디자인 패턴에서 자주 사용하는 상속, 다형성 등과 완전히 구분되죠.

1.3 멘탈 모델 비교

클래스와 위임에 대해 비교했으니 이번엔 두 코딩 방식인 (OO, OLOO)를 비교해 보겠습니다. 이는 5장에서도 잠깐 언급했었습니다.

function FOO(who) {
  this.me = who;
}
FOO.prototype.identify = function() {
  return 'I am' + this.me;
}

function Bar(who) {
  Foo.call(this, who);
}
Bar.prototype = Object.create(Foo.prototype);

Bar.prototype.speak = function() {
  alert('Hello ' + this.identify());
}

var b1 = new Bar('b1');
var b2 = new Bar('b2');

b1.speak();
b2.speak();

자식 클래스 Bar sms 부모 클래스 Foo를 상속한 뒤 b1, b2로 인스턴스화 합니다. 그 결과 b1은 Bar.prototype으로 위임되며 이는 다시 Foo.prototype으로 위임됩니다.

Foo = {
  init: function(who) {
    this.who = who
  },
  identify: function() {
    return 'i am ' + this.me;
  }
}

Bar = Object.create(Foo);
Bar.speak = function() {
  alert('Hello ' + this.identify())
}

var b1 = Object.create(Bar);
b1.init('b1');
var b2 = Object.create(Bar);
b2.init('b2');

b1.speak();
b2.speak();

앞에서는 [b1 -> Bar.prototype -> Foo.prototype] 방향으로 위임한 것처럼 이번에도 [b1 -> Bar -> Foo]로 [[Prototype]] 위임을 활용하며, 세 객체는 서로 연결되어 있게 되죠.

여기서 중요한 것은 보이는 코드만큼 잡다한 것들이 정리되고 단순해졌다는 사실이죠. 생성자, 프로토타입, new 호출을 하면서 클래스처럼 보이게 하려고 헷갈리기만 한 장치들을 쓰지 않고 그냥 객체를 서로 연결해 주기만 한 거죠. 

어차피 같은 기능이라면 클래스 스타일보다는 OLOO 스타일 코드를 쓰는 게 더 낫지 않을까요?

2. 인트 로스 펙션

클래스 지향 프로그래밍에서는 인스턴스를 조사해 객체 유형을 거꾸로 유추하는 타입 인트 로스 펙션에 익숙할 것입니다. 클래스 인스턴스에서 타입 인트 로스 펙션은 주로 인스턴스가 생성된 소스 객체의 구조와 기능을 추론하는 용도로 쓰이죠.

function Foo() {}
Foo.prototype.something = function() {}

function Bar() {}
Bar.prototype = Object.create(Foo.prototype);

var b1 = new Bar('b1');

Bar.prototype instanceof Foo // true
Object.getPrototypeOf(Bar.prototype) === Foo.prototype; // true
Foo.prototype.isPrototypeOf(Bar.prototype) // true

b1 instanceof Foo // true
b1 instanceof Bar // true
Object.getPrototypeOf(b1) === Bar.prototype; // true
Foo.prototype.isPrototypeOf(b1) // true
Bar.prototype.isPrototypeOf(b1) // true

흔히 말하는 '덕 타이핑'이라고 하여 많은 개발자들이 instanceof 보다 선호하는 또 다른 타입 인트 로스 펙션 방법도 있습니다. 이는 '오리처럼  보이는 동물이 오리 소리를 낸다면 오리가 분명하다'는 옛 속담에서 유래된 말이죠.

예를 들어 위임 가능한 something() 함수를 가진 객체와 a1의 관계를 애써 조사하는 대신(위처럼) a1.something을 테스트해보고 여길 통과하면 (a1에 직속된 메서드인지, 다른 객체에 위임되어 발견된 메서드인지 상관없이) a1 은. something()을 호출할 자격이 있다고 간주하는 것이죠. 이 자체는 큰 리스크는 없습니다. 그러나 '덕 타이핑'은 원래 테스트 결과 이외의 객체의 다른 기능까지 확장하여 추정하는 경향이 있어 위험하긴 합니다.

위와 달리 OLOO 스타일 코드의 타입 인트로스펙션은 훨씬 깔끔하다는 사실을 다시 보여주겠습니다.

var FOO = {}
var Bar = Object.create(Foo)

var b1 = Object.create(Bar);

Foo.isPrototypeOf(Bar) // true
Object.getPrototypeOf(Bar) === Foo // true

Foo.isPrototypeOf(b1) // true
Bar.isPrototypeOf(b1) // true
Object.getPrototypeOf(b1) === Bar // true

instaceOf 연산자는 괜스레 클래스가 뭔가 관련이 있는 것 같은 착각을 줄 수 있어서 사용하지 않았고요. 엔진에 던지는 질문은 대략 '당신은 나의 프로토타입 중 하나인 가요?' 정도입니다. Foo.prototype으로 에둘러 갈 필요도 없이, 장황하게 Foo.prototype.isPrototypeOf()로 수고롭게 물어볼 필요도 없죠.

 

정리

- 클래스와 송속은 소프트웨어 아키텍처를 설계할 때 선택하여도, 선택하지 않아도 되는 디자인 패턴의 하나입니다. 개발자 대부분은 클래스가 코드 체계를 잡는 유일무이한 방법이라고 여기는데, 이  장에서는 '작동 위임'이라는 디자인 패턴을 학습했습니다.

- 작동 위임 패턴은 객체를 부모/자식 클래스 관계가 아닌 동등한 입장에서 서로 위임하는 형태로 연결되어 있죠.

- 객체만으로 구성된 코드를 구성한다면 사용 구문도 단순해지고 아키텍처 또한 더 간단해질 수 있습니다.

- OLOO는 클래스라는 추상화 장치 없이도 직접 객체를 생성 및 연계합니다. 또한 [[Prototype]] 기반의 작동 위임을 아주 자연스럽게 구현합니다.

 

Comments