Prototype

raccoonback·2020년 6월 22일
1

javascript

목록 보기
6/11
post-thumbnail

이 글은 You Don't Know JS 서적을 참고하여 작성하였습니다.

[[Prototype]]

자바스크립트 모든 객체[[Prototype]] 내부 프로퍼티를 가진다.

그럼 [[Prototype]] 프로퍼티는 무엇일까?

[[Prototype]] 프로퍼티는 상위 객체와 연결을 해주는 역할을 한다.

그럼 [[Prototype]] 프로퍼티는 왜 필요한가?

앞서 [[Get]] 과정으로 객체에서 프로퍼티를 탐색한다고 말했었다. 찾고자 하는 프로퍼티가 현재 객체에 없으면, [[Prototype]]이 가리키는 객체를 연쇄적으로 따라가면서 탐색한다. 이와 같은 과정을 Prototype Chainning이라 부른다.

결론적으로, [[Prototype]] 프로퍼티는 Prototype Chainning으로 프로퍼티 탐색하기 위해 필요하다.

const foo = {
    name: '철수',
    bloodType: 'A'
};

const bar = Object.create(foo, {
    name: {
        value: '미애',
        writable: true,
        enumerable: true,
        configurable: true
    }
});

console.log(bar.name);
console.log(bar.bloodType);
// 미애
// A

Object.create() 함수는 두 번째 인자로 전달한 서술자로 새로운 객체를 생성하는 것 뿐 만 아니라, 새로운 객체의 [[Prototype]] 프로퍼티가 첫 번째 인자로 전달된 객체를 참조하도록 연결해 반환하는 함수이다. 뒤에서 자세하게 다룰 것이니 우선 이 정도로 이해하고 넘어가자.

어쨌거나 Object.create() 함수를 통해 반환된 새로운 객체를 bar 변수에 할당하고 있는 것이다.

또한, [[Prototype]] 프로퍼티는 foo 객체를 참조하고 있을 것이다. Chrome 브라우저를 통해 보면 좀 더 이해하기 쉬울 것이다.

그림을 통해서도 보이듯이, bar 객체는 name 프로퍼티를 가지고 있으므로 해당 프로퍼티의 값인 '미애' 텍스트가 출력된다. 하지만 bar 객체는 bloodType라는 프로퍼티가 없으므로, [[Prototype]] 프로퍼티가 참조하는 객체를 연쇄적으로 타고 올라가면서 bloodType 프로퍼티를 탐색한다.(Prototype Chainning) 결국, [[Prototype]] 프로토타입이 참조하는 foo 객체에서 bloodType 프로퍼티를 발견해 'A' 텍스트가 출력되는 것을 확인할 수 있다. 이러한 [[Get]] 과정은 프로퍼티을 찾을 때까지 연쇄적으로 [[Prototype]]을 타고 올라가 탐색한다. 만약 foo 객체에도 없었다면 결국 해당 프로퍼티를 찾지 못해 undefined를 출력했을 것이다.

Object.prototype

그럼 최종적으로 Prototype Chainning이 끝나는 시점은 어디일까?

Prototype Chainning 최종 종착역은 내장된 Object.prototype 객체이다. 즉, 자바스크립의 모든 객체는 Object.prototype 객체와 연결된 자손인 것이다.

자바스크립트 객체를 사용하다 보면 기본적으로 제공되는 공용 유틸리티를 보았을 것이다. toString(), hasOwnProperty() 함수들이 Object.prototype 객체에 포함되어 일반적인 객체들에게 제공되는 것이다.

여기서 한가지 정확하게 이해해야 하는 것은 Prototype Chainning의 마지막이 Object가 아닌 Object.prototype 객체라는 것이다.

실제로, Object는 함수이고 자바스크립에서 함수는 모두 객체로 이루어져 있기 때문에, Object 함수객체(MDN에서는 생성자라 부른다.)는 내부적으로 .prototype 프로퍼티를 통해 자신의 Object Prototype 객체를 가리키고, Object Prototype 객체.constructor 프로퍼티에 Object 함수를 참조하고 있다.

Object 함수를 이용해 새로운 객체를 생성하는 경우, Object 함수가 호출되고 새로운 객체가 생성돼 반환된다. 뿐만 아니라, 새로운 객체의 [[Prototype]] 프로퍼티는 Object Prototype 객체를 참조하고 있어 공용 유틸리티에 접근할 수 있다.

const newObject = new Object();

console.log(newObject, Object.getPrototypeOf(newObject), Object.prototype);
console.log(Object.getPrototypeOf(newObject) === newObject.__proto__);
console.log(newObject.__proto__ === Object.prototype);
console.log(Object.getPrototypeOf(newObject) === Object.prototype);
// {} {} {}
// true
// true
// true

조금 더 일반화해보면, 일반 함수Object 함수와 동일한 구성으로 동작한다는 것을 알 수 있다.

function Foo() {
    console.log('Foo');
}

const newFoo = new Foo();

console.log(newFoo, Object.getPrototypeOf(newFoo), Foo.prototype);
console.log(Object.getPrototypeOf(newFoo) === newFoo.__proto__);
console.log(newFoo.__proto__ === Foo.prototype);
console.log(Object.getPrototypeOf(newFoo) === Foo.prototype);
// Foo
// Foo {} Foo {} Foo {}
// true
// true
// true

프로퍼티 세팅과 가려짐

동일한 이름의 프로퍼티가 현재 가리키는 객체와 [[Prototype]] 연쇄를 통한 상위 수준의 객체 두 곳에 동시에 존재하는 경우, 항상 연쇄의 최하위 수준에서 먼저 발견된 프로퍼티로 인해 상위 수준의 프로퍼티가 참조되지 못하는 현상을 Shadowing(가려짐)라 부른다.

이러한 Shadowing(가려짐) 현상은 프로퍼티 세팅 과정에서 발생하는데 하나씩 살펴보자.

  1. Prototype Chainning을 통해서 동일한 이름의 프로퍼티를 발견하지 못한다면, 현재 객체에 프로퍼티를 세팅한다.

    const object = {};
    object.foo = '철수';

  2. Prototype Chainning을 통해서 동일한 이름의 프로퍼티를 발견하고, 상위 수준에서 발견된 프로퍼티가 쓰기 가능(writable: true)한 경우에는 현재 객체에 프로퍼티로 세팅되어 Shadowing(가려짐) 현상이 발생한다.

    const object = {};
    Object.defineProperty(object, 'foo', {
        value: '철수',
        writable: true, // 쓰기 가능
        enumerable: true,
        configurable: true
    
    });
    
    const childObject = Object.create(object);
    childObject.foo = '미애';
    
    console.log(childObject, object);
    // { foo: '미애' } { foo: '철수' }
    
    console.log(childObject.foo, object.foo);
    // 미애 철수
    
    console.log(childObject.hasOwnProperty('foo'), object.hasOwnProperty('foo'));
    // true true

  3. Prototype Chainning을 통해서 동일한 이름의 프로퍼티를 발견하고, 상위 수준에서 발견된 프로퍼티가 쓰기 불가능(writable: false)한 경우에는 상위 수준과 현재 객체 모두에 프로퍼티가 세팅되지 않고 무시(에러)가 된다. 즉, Shadowing(가려짐) 현상이 발생하지 않는다.

    const object = {};
    Object.defineProperty(object, 'foo', {
        value: '철수',
        writable: false, // 쓰기 불가
        enumerable: true,
        configurable: true
    
    });
    
    const childObject = Object.create(object);
    childObject.foo = '미애';
    
    console.log(childObject, object);
    // {} { foo: '철수' }
    
    console.log(childObject.foo, object.foo);
    // 철수 철수
    
    console.log(childObject.hasOwnProperty('foo'), object.hasOwnProperty('foo'));
    // false true

  4. 마지막으로, Prototype Chainning을 통해서 상위 수준에서 발견한 프로퍼티가 Setter인 경우에는 항상 해당 세터 함수가 호출된다. 결국, Shadowing(가려짐) 현상이 발생하지 않는 것 뿐 만 아니라 Setter 함수가 재정의되지도 않는다.

    const object = {};
    Object.defineProperty(object, 'foo', {
        set: function(value) {
            object.value = 'Parent_' + value;
        },
        get: function() {
          return object.value;
        },
        enumerable: true,
        configurable: true
    
    });
    
    const childObject = Object.create(object);
    childObject.foo = '철수';
    
    console.log(childObject, object);
    // {} { foo: [Getter/Setter], value: 'Parent_철수' }
    
    console.log(childObject.foo, object.foo);
    // Parent_철수 Parent_철수
    
    console.log(childObject.hasOwnProperty('foo'), object.hasOwnProperty('foo'));
    // false true

Shadowing(가려짐)은 코드를 복잡하게 하고 애매한 부분이 있으므로 되도록 사용하지 않는 것을 권장한다.

const object = {count: 0};
const childObject = Object.create(object);

console.log(childObject.count, object.count);
// 0 0

console.log(childObject.hasOwnProperty('count'), object.hasOwnProperty('count'));
// false true

childObject.count++;    // 가려짐 발생, childObject 객체에 count 프로퍼티 추가됨

console.log(childObject.count, object.count);
// 1 0

console.log(childObject.hasOwnProperty('count'), object.hasOwnProperty('count'));
// true true

childObject.count++object 객체를 찾아 1을 추가할 것같지만, 실제로는 childObject.count = childObject.count + 1 연산과정을 거치기 때문에, object 객체로 참조한 count 프로퍼티 값에 1을 더하여 childObject 객체 프로퍼티로 추가된다. 결국, 가려짐 현상이 발생하기 때문에 위임을 통해 프로퍼티 수정할 때에는 object.count++로 해야된다.

.prototype

자바스크립트 모든 함수는 기본적으로 .prototype라는 공용/열거 불가한 프로퍼티를 가진다.

function Foo() { }

console.log(Foo, Foo.prototype);
// [Function: Foo] Foo {}

보통Foo.prototype을 Foo의 프로토타입이라고 부르는데, new Foo()로 생성한 모든 객체는 Foo.prototype가 가리키는 객체와 [[Prototype]] 링크로 연결된다.

구체적으로 new 키워드를 이용해서 새로운 객체를 생성하는 경우, 생성된 객체는 Foo.prototype가 가리키는 객체를 내부 [[Prototype]] 프로퍼티와 연결힌다.

function Foo() {
    console.log("I'm constructor!");
}

const foo = new Foo();
console.log(Object.getPrototypeOf(foo) === Foo.prototype);
// I'm constructor!
// true

결국 새로운 객체는 다른 언어에서의 클래스와 같이 작동이 복사되는 것이 아니라, [[Prototype]]Foo.prototype 객체와 연결된 것이 전부이다.

즉, 자바스크립트는 객체 프로퍼티를 복사하지 않고 두 객체를 링크로 연결해 상호간에 프로퍼티/함수에 접근할 수 있도록 위임한다.

생성자

new 키워드를 통해 객체 생성시 호출되는 함수는 다른 언어에서의 생성자와 비슷해 보인다.

뿐 만 아니라, Foo.prototype 객체에는 기본적으로 열거 불가한 공용 프로퍼티로 .constructor가 아래와 같이 구성되어 새로 생성된 객체와 함수를 연결한다.

하지만, new 키워드를 통해 객체 생성시 호출되는 함수는 다른 언어에서의 생성자가 아니라, 보통의 일반적인 함수일 뿐이다.

구체적으로, new 키워드는 일반함수 호출을 가로채어 원래 수행할 작업외에 "객체 생성"을 추가로 수행시키는 지시자인 것이다.

위 그림을 토대로 코드를 만들어보자.

function Foo() {
    console.log("I'm constructor!");
}

const foo = new Foo();
console.log(foo);
// I'm constructor!
// Foo {}

Foo 함수는 그저 평범한 함수일 뿐이고, new 키워드를 사용해 호출한 함수를 생성자라고 부른다. 즉, 함수는 생성자가 아니지만, new 이용해 함수를 호출하면 부수적으로 객체가 생성되며 이를 생성자 호출이라 말한다.

체계

자바스크립트는 클래스 지향을 흉내내기 노력해왔지만, 이는 실제로 내부적으로 함수 프로퍼티/함수 복사와 같은 일은 일어나지 않고 모두 프로토타입으로 연결돼있다.

function Foo(name) {
    this.name = name;
}

Foo.prototype.getName = function() {
  return this.name;
};

const fooA = new Foo('철수');
const fooB = new Foo('미애');

console.log(fooA.getName(), fooB.getName());
// 철수 미애

fooA, fooB 객체 생성시, Foo.prototype 객체의 프로퍼티/함수가 모두 복사되어 각각의 객체의 함수인 getName() 함수가 호출될 것이라 예상하겠지만, 절대로 새로 생성된 객체에 프로퍼티/함수가 복사되는 일은 일어나지 않는다.

fooA, fooB 객체 생성시 단순히 각자 내부 [[Prototype]] 프로퍼티가 Foo.prototype에 연결될 뿐이다.

따라서 name 프로퍼티는 생성된 객체에 각각 구성되지만, getName() 함수는 Foo.prototype에 위치한다.

다음으로, constructor 프로퍼티는 항상 생성자 의미로 Foo 함수로 가리키고 있을까? 정답은 아니다.

function Foo() { }

Foo.prototype = { };

const foo = new Foo();

console.log(foo.constructor === Foo);
console.log(foo.constructor === Object);
// false
// true

위 예제를 보자.

Foo.prototype 객체는 새로운 객체가 할당되고 constructor 프로퍼티는 존재하지 않기 때문에, 생성된 fooA 객체는 Foo 함수를 참조할 수 없다. 또한, Prototype Chainning으로 constructor 프로퍼티를 탐색하다가 결국 마지막인 Object 객체의 constructor 프로퍼티인 Object() 함수를 참조하는 것이다.

따라서, foo.constructor는 매우 불안정하고 신뢰할 수 없는 레퍼런스이므로 코드에서 직접 사용하지 않는 것을 권장한다.

프로토타입 상속

Object.create()를 사용한 프로토타입 상속을 살펴보자.

function Foo(name) {
    this.name = name;
}

Foo.prototype.getName = function() {
  return this.name;
};

function Bar(name, age) {
    Foo.call(this, name);
    this.age = age;
}

Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.getAge = function() {
    return this.age;
};

const bar = new Bar('철수', 28);
console.log(bar.getName(), bar.getAge());
// 철수 28

Bar 함수는 기본적으로 .prototype 링크를 자신의 객체에 가지고 있는데, Foo 함수와 상속과 유사한 구조를 만들기 위해 Object.create(Foo.prototype)의 반환값으로 재할당하였다.

그럼 Object.create() 함수는 무엇일까?

Object.create() 함수을 간단하게 표현하면 아래와 같다.

Object.create = function(o) {
	function F() { }
	F.prototype = o;
	return new F();
}

의사 코드를 살펴보면, 임시함수 F를 이용해서 F.prototype 프로퍼티가 링크하려는 객체(o)를 가리키도록 오버라이드하고, new F()으로 원하는 연결이 수립된 새로운 객체를 생성해 반환한다.

즉, 인자로 전달받은 o 객체와 [[Prototype]]으로 연결된 객체를 생성해 반환하는 것이다.

다시 돌아와서, Bar.prototype = Object.create(Foo.prototype) 코드는 Foo.prototype 객체를 [[Prototype]]으로 가리키는 새로운 객체를 생성해 Bar.prototype에 할당하는 것으로 이해할 수 있을 것이다.

따라서, Foo.prototypeBar.prototype 객체는 상속과 유사한 구조로 연결된다.

여기서, "그냥 Bar.prototype = Foo.prototype와 같이 동일한 프로토타입 객체를 가리키면 안될까?"하는 의문이 들 것이다. 하지만, 이런 경우에는 Foo.prototype가 예기치 못하게 영향을 받을 수 있기 때문에 지양해야만 한다. 예를 들어, 동일한 프로토타입 객체를 가리키게 되면 Bar.prototype.getAge = function() { ... }에서의 getAge() 함수는 Foo.prototype 에 등록되는 것과 같이 영향을 미치게 된다.

그러므로 개발자는 Foo()의 부수적인 효과가 발생하지 안도록 Object.create() 함수를 잘 사용해서 새로운 객체를 적절히 링크하여 생성해야만 한다.

ES6 부터는 이러한 부담을 줄여주기 위해 Object.setPrototypeOf() 유틸리티가 도입되면서 드디어 예측 가능하고 가독성이 좋은 표준 방법이 마련이 되었다.

// 기존
Bar.prototype = Object.create(Foo.prototype);

// ES6 이후
Object.setPrototypeOf(Bar.prototype, Foo.prototype);

관계 조사

클래스 지향 언어에서는 객체간의 상속 계통을 알아보는 것을 인트로스펙션(Instospection, 리플렉션)이라 부른다.

자바스크립트에서 상속 계통을 의미하는 위임 링크는 어떻게 확인할 수 있을까?

가장 쉽게 떠올리는 것은 instanceof 연산자일 것이다. 하지만, 자바스크립트에서 instanceof 연산자는 객체의 [[Prototype]]을 연쇄적으로 순회하면서 대상 객체의 프로토타입이 동일한지 조사한다.

// foo 객체의 [[Prototype]]을 연쇄적으로 순회하면서
// Foo.prototype 존재 여부 확인
// 즉, bar.__proto__ === Foo.prototype 조사
foo instanceof Foo

즉, 만약 두 개의 객체가 있다면 instanceof 연산자만으로는 둘이 동일한 상속 계통인가를 파악할 수 없다.

따라서 [[Prototype]]의 상속 계통을 확인할 수 있는 권장 방법은 isPrototypeOf() 함수를 이용하는 것이다.

isPrototypeOf() 함수는 전달받은 인자의 전체 [[Prototype]] 연쇄에 호출한 객체가 있는지 확인한다.

결국, 아래와 같은 방법으로 두 객체의 동일한 상속 계통 여부를 간단하고 명확하게 확인할 수 있다.

// bar [[Prototype]] 연쇄 어딘가에 Foo.prototype 객체가 존재하는가?
Foo.prototype.isPrototypeOf(bar);

뿐만 아니라, ES6부터는 getPrototypeOf(), setPrototypeOf() 함수를 지원하여 객체의 프로토타입을 조회/수정할 수 있다.([[Prototype]] 수정하지 않는 것을 권장..)

function Foo() { }

function Bar() { }

Bar.prototype = Object.create(Foo.prototype);

const foo = new Foo();
const bar = new Bar();

console.log(foo instanceof Bar);
console.log(bar instanceof Foo);
// false
// true

console.log(Object.getPrototypeOf(bar).isPrototypeOf(foo));
console.log(Object.getPrototypeOf(foo).isPrototypeOf(bar));
// false
// true

객체 링크

[[Prototype]] 체계는 다른 객체를 참조하는 어떠한 객체에 존재하는 내부 링크이다. 객체의 프로퍼티/메서드를 참조하려고 하는데, 해당 객체에 존재하지 않은 경우 동작하게 된다. 이러한 과정을 앞에서 언급한 Prototype Chain 이라 부른다.

링크 생성

두 객체에 의미 있는 관계를 맺어주는 데 클래스가 필수적인 것은 아니다. 객체의 위임 연결만으로도 가능하며, Object.create() 함수를 이용해 깔끔하게 처리할 수 있다.

const foo = {
    log: function() {
        console.log("Hello World!");
    }
};

const bar = Object.create(foo);
bar.log();

Object.create() 함수는 새로운 객체를 생성하고 foo 객체와 연결한다. 프로토타입 상속 구조뿐 만 아니라 단순히 객체간의 상속 구조속에서도 [[Prototype]] 체계의 힘을 실감할 수 있다.

Object.create(null)[[Prototype]] 링크가 빈(null) 객체를 생성한다. 반환된 객체는 Prototype Chain의 종착역은 Object가 아닌 null이다. 즉, 해당 객체는 위임된 프로퍼티/함수로부터 어떠한 영향도 받을 일이 없기 때문에, 주로 딕셔너리 데이터 저장소로 사용한다.

링크는 대비책?

지금까지 살펴본 [[Prototype]] 체계를 프로퍼티/함수를 발견하지 못했을 경우의 대비책으로 생각할 수 있는데, 이는 잘못된 생각이다.

[[Prototype]] 체계를 이용해 현재에는 에러없이 동작할 지 몰라도, 점차 코드 분석과 유지 보수에 어려움을 겪게 될 것이다.

const foo = {
    log: function() {
        console.log("Hello World!");
    }
};

const bar = Object.create(foo);
bar.log();
// Hello World!

위와 같이, bar.log() 함수 호출시 bar 객체에 log() 메서드가 없어도 정상적으로 처리되게 API 설계를 하면, 추후 유지 보수 담당자는 코드 분석에 어려움을 겪을 것이다.

bar 객체에 보다 명시적인 API를 설계하고 내부적으로 [[Prototype]] 체계를 이용하는 위임 디자인 패턴 사용하는 것을 권장한다. 즉, 아래와 같이 bar 객체에 상위 수준 객체(foo)의 log() 메서드를 참조하는 greet() 메서드를 정의하면 코드 분석이 용이해지고 유지 보수에 도움이 될 것이다.

const foo = {
    log: function() {
        console.log("Hello World!");
    }
};

const bar = Object.create(foo);
bar.greet = function() {
    this.log(); // 내부적으로 위임
};

bar.greet();

작동 위임

지금까지 학습한 프로토타입을 요약해보면, [[Prototype]] 체계는 한 객체가 다른 객체를 참조하기 위한 내부 링크라는 것이다.
이러한 링크는 처음 참조하는 객체에 존재하지 않는 프로퍼티/메서드를 참조하려고 할 경우 사용되는데, 자바스크립트 엔진은 [[Prototype]] 링크를 따라가며 연결된 객체마다 찾고자하는 프로퍼티/메서드가 존재하는지 확인한다.
만약에 프로퍼티가 발견되지 않으면 다음 [[Prototype]] 링크에 연결된 객체를 타고 이동하며 탐색은 계속된다. 이러한 과정으로 객체 간 연결고리가 머릿속에 그려질 것이다. 그것이 바로 프로토타입 연쇄이다.

위임 이론

고전적인 프로토타입 상속 방법은 각 함수의 프로토타입(.prototype)을 연결하여 위임하는 방식이다. 하지만, 두 프로토타입을 연결하는 과정이 복잡하며 관리가 녹록치 않다.
따라서 저자는 OLOO(Objects Linked to Other Objects) 스타일 코드를 권장하는데, OLOO객체를 다른 객체에 연결하여 작동을 위임하는 방법을 말한다.

작동 위임이란 찾으려는 프로퍼티/메서드 레퍼런스가 객체에 없으면 [[Prototype]]으로 연결된 다른 객체에서 수색 작업을 하도록 위임하는 것을 의미한다.

구체적인 예제를 통해 살펴보자.

const Job = {
    setID(id) {
        this.id = id;
    },
    outputID() {
        console.log(this.id);
    }
};

// Driver가 Job에 위임
const Driver = Object.create(Job);

Driver.drive = function(id, location) {
    this.setID(id);
    this.location = location;
};

Driver.output = function() {
    this.outputID();
    console.log(this.id + "가 목적지 " + this.location + "로 이동중입니다.");
};

const driver = Object.create(Driver);
driver.drive('철수', '제주도');
driver.output();
// 철수
// 철수가 목적지 제주도로 이동중입니다.

// Cleaner가 Job에 위임
const Cleaner = Object.create(Job);

Cleaner.sweep = function(id, that) {
    this.setID(id);
    this.that = that;
};

Cleaner.output = function() {
    this.outputID();
    console.log(this.id + "가 " + this.that + "를(을) 청소하고 있습니다.");
};

const cleaner = Object.create(Cleaner);
cleaner.sweep('미애', '선풍기');
cleaner.output();
// 미애
// 미애가 선풍기를(을) 청소하고 있습니다.

Job 객체는 다양한 유틸리티 메서드를 포함하고 있고, 각각의 작업(Driver, Cleaner)은 고유한 데이터와 작동을 정의하고 있다.
또한 각각의 작업(Driver, Cleaner)은 Job 유틸리티 객체와 연결하여, 특정 작업 객체(driver, cleaner)가 Job 객체에 작동을 위임하도록 구성하였다.

위 구성도를 통해 보시다 싶이, 평범한 객체가 다른 객체와 [[Prototype]]으로 연결되어 작동을 위임하는 OLOO 코드 스타일로 구성된 것이다.

이제 OLOO 스타일 특징을 하나씩 살펴보자.

  1. idlocation(that) 두 데이터 멤버는 Job가 아닌 Driver(Cleaner)의 직속 프로퍼티이다. 일반적으로 [[Prototype]] 위임시 상태값은 위임하는 쪽(Driver, Cleaner)에 두고 위임받는 쪽(Job)에는 두지 않는다.
  2. 서로 다른 수준의 [[Prototype]] 연쇄에서 동일한 명칭이 뒤섞이는 일은 피해야 한다. 같은 이름으로 충돌하면 레퍼런스를 정확히 참조할 수 없고 취약한 구문이 만들어지므로, 작동 위임 패턴에서는 각 객체의 작동 방식을 잘 설명하는 서술적인 메서드 명칭이 필요하다. 그래야 코드의 가독성과 유지 보수성이 높아진다.
  3. 마지막으로, Driver(Cleaner) 객체가 Job 객체에 작동을 위임하므로 Job이 가진 일반적인 유틸리티 메서드는 Driver(Cleaner)가 얼마든지 이용할 수 있다는 것이다. this.setID(id)Driver 객체 내부에서 setID()를 찾지만 Driver에는 해당 메서드가 없으므로 [[Prototype]] 위임 링크가 체결된 Job 객체로 이동하여 setID() 메서드를 발견한다. 그리고 암시적 호출부에 따라 this 바인딩 규칙은 Job 객체에서 발견된 메서드지만 setID() 실행시 thisDriver으로 바인딩된다.

OLOO 스타일은 고전적인 방법과 비교해 '다른 객체와의 연결에만 집중'하면 되므로 고민할 요소가 줄어들고 구조가 간결해진다는 것을 알 수 있다.

뿐만 아니라, 작동 위임은 상세한 내부 구현을 숨기는 용도로 적절하며 API 인터페이스에 직접 노출하지 않는다. 위 예시에도 API 사용자가 Driver.setID()를 호출하게 할 필요는 없으므로, Driver.drive() 함수가 Job.setID()에 위임한 내부 사정은 사용자에게 감추어 내부 구현을 숨길 수 있다.

References

profile
한번도 실수하지 않은 사람은, 한번도 새로운 것을 시도하지 않은 사람이다.

0개의 댓글