Chapter 6: Behavior Delegation - hochan222/Everything-in-JavaScript GitHub Wiki

5μž₯에 λŒ€ν•œ μ •λ¦¬λ‘œμ„œ Prototype λ©”μ»€λ‹ˆμ¦˜μ€ λ‹€λ₯Έ 객체λ₯Ό μ°Έμ‘°ν•˜λŠ” ν•˜λ‚˜μ˜ 객체에 μ‘΄μž¬ν•˜λŠ” λ‚΄λΆ€ 링크이닀. 일련의 객체간 연결은 ν”„λ‘œν† νƒ€μž… 체인을 ν˜•μ„±ν•œλ‹€.

Towards Delegation-Oriented Design

Class Theory

μ†Œν”„νŠΈμ›¨μ–΄μ—μ„œ λͺ¨λΈλ§ν•΄μ•Όν•˜λŠ” μœ μ‚¬ν•œ μž‘μ—…[Task]( "XYZ", "ABC"λ“±)이 μ—¬λŸ¬ 개 μžˆλ‹€κ³  κ°€μ • ν•΄λ³΄μž.
클래슀λ₯Ό μ‚¬μš©ν•˜μ—¬ μ‹œλ‚˜λ¦¬μ˜€λ₯Ό μ„€κ³„ν•˜λŠ” 방법은 λ‹€μŒκ³Ό κ°™λ‹€.
Task와 같은 일반 μƒμœ„(κΈ°λ³Έ) 클래슀λ₯Ό μ •μ˜ν•˜κ³  λͺ¨λ“  "μœ μ‚¬" μž‘μ—…μ— λŒ€ν•œ 곡유 λ™μž‘μ„ μ •μ˜ν•œλ‹€.
그런 λ‹€μŒ μžμ‹ 클래슀 XYZ 및 ABCλ₯Ό μ •μ˜ν•œλ‹€.
두 ν΄λž˜μŠ€λŠ” λͺ¨λ‘ Taskμ—μ„œ μƒμ†λ˜κ³  각 ν΄λž˜μŠ€λŠ” 각각의 μž‘μ—…μ„ μ²˜λ¦¬ν•˜λŠ” 특수 λ™μž‘μ„ μΆ”κ°€ν•œλ‹€.
μ€‘μš”ν•˜κ²Œλ„ 클래슀 λ””μžμΈ νŒ¨ν„΄μ€ 상속을 μ΅œλŒ€ν•œ ν™œμš©ν•˜κΈ° μœ„ν•΄ λ©”μ„œλ“œ μž¬μ •μ˜ (및 λ‹€ν˜•μ„±)λ₯Ό μ‚¬μš©ν•˜λ„λ‘ μž₯λ €ν•œλ‹€.
λ§ˆμ§€λ§‰μœΌλ‘œ ν•˜λ‚˜ μ΄μƒμ˜ XYZ μžμ‹ 클래슀 볡사본을 μΈμŠ€ν„΄μŠ€ν™”ν•˜κ³  ν•΄λ‹Ή μΈμŠ€ν„΄μŠ€λ₯Ό μ‚¬μš©ν•˜μ—¬ "XYZ"μž‘μ—…μ„ μˆ˜ν–‰ν•œλ‹€.

class Task {
	id;

	// constructor `Task()`
	Task(ID) { id = ID; }
	outputTask() { output( id ); }
}

class XYZ inherits Task {
	label;

	// constructor `XYZ()`
	XYZ(ID,Label) { super( ID ); label = Label; }
	outputTask() { super(); output( label ); }
}

class ABC inherits Task {
	// ...
}

μΈμŠ€ν„΄μŠ€μ—λŠ” 일반 μž‘μ—… μ •μ˜ λ™μž‘κ³Ό νŠΉμ • XYZ μ •μ˜ λ™μž‘μ˜ 볡사본이 λͺ¨λ‘ μžˆλ‹€.
생성 ν›„μ—λŠ” 각 μΈμŠ€ν„΄μŠ€κ°€ μ˜λ„ ν•œ μž‘μ—…μ„ μˆ˜ν–‰ν•˜λŠ” 데 ν•„μš”ν•œ λͺ¨λ“  λ™μž‘μ˜ 볡사본을 가지고 μžˆμœΌλ―€λ‘œ 일반적으둜 μ΄λŸ¬ν•œ μΈμŠ€ν„΄μŠ€μ™€ 만 μƒν˜Έ μž‘μš©ν•œλ‹€(ν΄λž˜μŠ€κ°€ μ•„λ‹ˆλΌ).

Delegation Theory

λ¨Όμ € TaskλΌλŠ” 객체λ₯Ό μ •μ˜ν•˜κ³  λ‹€μ–‘ν•œ μž‘μ—…μ΄ μ‚¬μš©ν•  μˆ˜μžˆλŠ” μœ ν‹Έλ¦¬ν‹° λ©”μ„œλ“œλ₯Ό ν¬ν•¨ν•˜λŠ” ꡬ체적인 λ™μž‘μ„ κ°–κ²Œλœλ‹€.
그런 λ‹€μŒ 각 μž‘μ—… ( "XYZ", "ABC")에 λŒ€ν•΄ ν•΄λ‹Ή μž‘μ—… 별 데이터 / λ™μž‘μ„ μ €μž₯ν•  개체λ₯Ό μ •μ˜ν•œλ‹€.
μž‘μ—… 별 개체λ₯Ό μž‘μ—… μœ ν‹Έλ¦¬ν‹° κ°œμ²΄μ— μ—°κ²°ν•˜μ—¬ ν•„μš”ν•  λ•Œ μœ„μž„ ν•  수 μžˆλ‹€.
기본적으둜 "XYZ"μž‘μ—…μ„ μˆ˜ν–‰ν•˜λ €λ©΄ 두 ν˜•μ œ / λ™λ£Œ 개체 (XYZ 및 μž‘μ—…)의 λ™μž‘μ΄ ν•„μš”ν•˜λ‹€κ³  μƒκ°ν•œλ‹€.
κ·ΈλŸ¬λ‚˜ 클래슀 볡사본을 톡해 ν•¨κ»˜ ꡬ성 ν•  ν•„μš”μ—†μ΄ λ³„λ„μ˜ κ°œμ²΄μ— 보관할 수 있으며 ν•„μš”ν•  λ•Œ XYZ κ°œμ²΄κ°€ Task에 μœ„μž„ ν•  수 μžˆλ‹€.

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

// make `XYZ` delegate to `Task`
var XYZ = Object.create( Task );

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

XYZ.outputTaskDetails = function() {
	this.outputID();
	console.log( this.label );
};

// ABC = Object.create( Task );
// ABC ... = ...

이 μ½”λ“œμ—μ„œ Task와 XYZλŠ” 클래슀 (λ˜λŠ” ν•¨μˆ˜)κ°€ μ•„λ‹ˆλΌ 객체 일뿐이닀.
XYZλŠ” Object.create (..)λ₯Ό 톡해 Task 객체에 λŒ€ν•œ Prototype λŒ€λ¦¬μžλ‘œ μ„€μ •λœλ‹€.
클래슀 지ν–₯ (일λͺ… OO-객체 지ν–₯)κ³Ό 비ꡐ할 λ•Œμ΄ μ½”λ“œ μŠ€νƒ€μΌμ„ "OLOO"(λ‹€λ₯Έ 객체에 μ—°κ²°λœ 객체)라고 λΆ€λ₯Έλ‹€.
JavaScriptμ—μ„œ Prototype λ©”μ»€λ‹ˆμ¦˜μ€ 개체λ₯Ό λ‹€λ₯Έ κ°œμ²΄μ— μ—°κ²°ν•œλ‹€.

Behavior Delegation: 객체 (XYZ)μ—μ„œ 찾을 μˆ˜μ—†λŠ” 경우 일뢀 객체 (XYZ)κ°€ 속성 λ˜λŠ” λ©”μ„œλ“œ 참쑰에 λŒ€ν•œ μœ„μž„ (νƒœμŠ€ν¬)을 μ œκ³΅ν•˜λ„λ‘ν•œλ‹€.

이것은 λΆ€λͺ¨μ™€ μžμ‹ 클래슀, 상속, λ‹€ν˜•μ„± λ“±μ˜ κ°œλ…κ³ΌλŠ” 맀우 λ‹€λ₯Έ 맀우 κ°•λ ₯ν•œ λ””μžμΈ νŒ¨ν„΄μ΄λ‹€.
λΆ€λͺ¨κ°€ μžμ‹μœΌλ‘œ ν˜λŸ¬κ°€λŠ” μƒνƒœμ—μ„œ 객체λ₯Ό 수직으둜 κ΅¬μ„±ν•˜λŠ” λŒ€μ‹  객체λ₯Ό λ‚˜λž€νžˆ μžˆλ‹€κ³  생각할 수 μžˆλ‹€.

Mutual Delegation (Disallowed)

λ‘˜ μ΄μƒμ˜ κ°œμ²΄κ°€ μ„œλ‘œμ—κ²Œ (μ–‘λ°©ν–₯으둜) μœ„μž„λ˜λŠ” μ£ΌκΈ°λ₯Ό λ§Œλ“€ 수 μ—†λ‹€.
Bλ₯Ό A에 μ—°κ²° ν•œ λ‹€μŒ Aλ₯Ό B에 μ—°κ²°ν•˜λ €κ³ ν•˜λ©΄ 였λ₯˜κ°€ λ°œμƒν•œλ‹€.

엔진 κ΅¬ν˜„μžκ°€ κ°μ²΄μ—μ„œ 속성을 쑰회 ν•  λ•Œλ§ˆλ‹€ ν•΄λ‹Ή κ°€λ“œ κ²€μ‚¬μ˜ μ„±λŠ₯ 적쀑을 ν•„μš”λ‘œν•˜λŠ” 것보닀 μ„€μ • μ‹œκ°„μ— ν•œ 번 λ¬΄ν•œ μˆœν™˜ μ°Έμ‘°λ₯Ό 확인 (및 κ±°λΆ€)ν•˜λŠ” 것이 더 μ„±λŠ₯이 μ’‹λ‹€λŠ” 것을 κ΄€μ°°ν–ˆκΈ° λ•Œλ¬Έμ— ν—ˆμš©λ˜μ§€ μ•ŠλŠ”λ‹€.

Debugged

function Foo() {}

var a1 = new Foo();

a1; // Foo {}

ν¬λ‘¬μ—μ„œλŠ” λ‹€μŒκ³Ό 같이 λ‚˜μ˜€λŠ” 반면 firefoxμ—μ„œλŠ” Object {} 둜 λ‚˜μ˜¨λ‹€.

Chrome은 본질적으둜 "{}λŠ” 이름이 'Foo'인 ν•¨μˆ˜λ‘œ κ΅¬μ„±λœ 빈 κ°œμ²΄μž…λ‹ˆλ‹€."라고 λ§ν•˜κ³ , νŒŒμ΄μ–΄ ν­μŠ€λŠ” "{}λŠ” Object의 일반적인 κ΅¬μ„±μ˜ 빈 κ°μ²΄μž…λ‹ˆλ‹€"라고 λ§ν•œλ‹€. λ―Έλ¬˜ν•œ 차이점은 Chrome이 λ‚΄λΆ€ μ†μ„±μœΌλ‘œ ꡬ성을 μˆ˜ν–‰ ν•œ μ‹€μ œ κΈ°λŠ₯의 이름을 적극적으둜 μΆ”μ ν•˜λŠ” 반면 λ‹€λ₯Έ λΈŒλΌμš°μ €λŠ” ν•΄λ‹Ή μΆ”κ°€ 정보λ₯Ό μΆ”μ ν•˜μ§€ μ•ŠλŠ”λ‹€.

function Foo() {}

var a1 = new Foo();

a1.constructor; // Foo(){}
a1.constructor.name; // "Foo"

κ·Έλ ‡λ‹€λ©΄ 크둬은 λ‹¨μˆœνžˆ constructor.name을 μΆ”μ ν•˜λŠ”κ°€? λ§žλ‹€, μ•„λ‹ˆλ‹€ λ‘˜λ‹€ 정닡이 될 수 μžˆλ‹€.

function Foo() {}

var a1 = new Foo();

Foo.prototype.constructor = function Gotcha(){};

a1.constructor; // Gotcha(){}
a1.constructor.name; // "Gotcha"

a1; // Foo {}

a1.constructor.name을 ν•©λ²•μ μœΌλ‘œ λ‹€λ₯Έ μ΄λ¦„μœΌλ‘œ 변경해도 μ—¬μ „νžˆ Foo이닀.

OLOO-style둜 λ‹€μ‹œλ³΄μž.

var Foo = {};

var a1 = Object.create( Foo );

a1; // Object {}

Object.defineProperty( Foo, "constructor", {
	enumerable: false,
	value: function Gotcha(){}
});

a1; // Gotcha {}

μ—¬κΈ°μ„œ Chrome μ½˜μ†”μ€ .constructor.name을 μ°Ύμ•„μ„œ μ‚¬μš©ν–ˆμŠ΅λ‹ˆλ‹€. μ‹€μ œλ‘œ 이 책을 μ“°λŠ” λ™μ•ˆμ΄ μ •ν™•ν•œ λ™μž‘μ€ Chromeμ—μ„œ λ²„κ·Έλ‘œ ν™•μΈλ˜μ—ˆλ‹€.

Mental Models Compared

OO μŠ€νƒ€μΌ

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();

OLOO style

var Foo = {
	init: function(who) {
		this.me = who;
	},
	identify: function() {
		return "I am " + this.me;
	}
};

var 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();

Classes vs. Objects

싀전이닀. λ¨Όμ € ν”„λ‘ νŠΈ μ—”λ“œ μ›Ή 개발의 일반적인 μ‹œλ‚˜λ¦¬μ˜€ 인 UI μœ„μ ― (λ²„νŠΌ, λ“œλ‘­ λ‹€μš΄ λ“±) 생성을 μ‚΄νŽ΄λ³΄μž.

Widget "Classes"

// Parent class
function Widget(width,height) {
	this.width = width || 50;
	this.height = height || 50;
	this.$elem = null;
}

Widget.prototype.render = function($where){
	if (this.$elem) {
		this.$elem.css( {
			width: this.width + "px",
			height: this.height + "px"
		} ).appendTo( $where );
	}
};

// Child class
function Button(width,height,label) {
	// "super" constructor call
	Widget.call( this, width, height );
	this.label = label || "Default";

	this.$elem = $( "<button>" ).text( this.label );
}

// make `Button` "inherit" from `Widget`
Button.prototype = Object.create( Widget.prototype );

// override base "inherited" `render(..)`
Button.prototype.render = function($where) {
	// "super" call
	Widget.prototype.render.call( this, $where );
	this.$elem.click( this.onClick.bind( this ) );
};

Button.prototype.onClick = function(evt) {
	console.log( "Button '" + this.label + "' clicked!" );
};

$( document ).ready( function(){
	var $body = $( document.body );
	var btn1 = new Button( 125, 30, "Hello" );
	var btn2 = new Button( 150, 40, "World" );

	btn1.render( $body );
	btn2.render( $body );
} );

ES6 class sugar

class Widget {
	constructor(width,height) {
		this.width = width || 50;
		this.height = height || 50;
		this.$elem = null;
	}
	render($where){
		if (this.$elem) {
			this.$elem.css( {
				width: this.width + "px",
				height: this.height + "px"
			} ).appendTo( $where );
		}
	}
}

class Button extends Widget {
	constructor(width,height,label) {
		super( width, height );
		this.label = label || "Default";
		this.$elem = $( "<button>" ).text( this.label );
	}
	render($where) {
		super.render( $where );
		this.$elem.click( this.onClick.bind( this ) );
	}
	onClick(evt) {
		console.log( "Button '" + this.label + "' clicked!" );
	}
}

$( document ).ready( function(){
	var $body = $( document.body );
	var btn1 = new Button( 125, 30, "Hello" );
	var btn2 = new Button( 150, 40, "World" );

	btn1.render( $body );
	btn2.render( $body );
} );

Delegating Widget Objects

var Widget = {
	init: function(width,height){
		this.width = width || 50;
		this.height = height || 50;
		this.$elem = null;
	},
	insert: function($where){
		if (this.$elem) {
			this.$elem.css( {
				width: this.width + "px",
				height: this.height + "px"
			} ).appendTo( $where );
		}
	}
};

var Button = Object.create( Widget );

Button.setup = function(width,height,label){
	// delegated call
	this.init( width, height );
	this.label = label || "Default";

	this.$elem = $( "<button>" ).text( this.label );
};
Button.build = function($where) {
	// delegated call
	this.insert( $where );
	this.$elem.click( this.onClick.bind( this ) );
};
Button.onClick = function(evt) {
	console.log( "Button '" + this.label + "' clicked!" );
};

$( document ).ready( function(){
	var $body = $( document.body );

	var btn1 = Object.create( Button );
	btn1.setup( 125, 30, "Hello" );

	var btn2 = Object.create( Button );
	btn2.setup( 150, 40, "World" );

	btn1.build( $body );
	btn2.build( $body );
} );

OLOO μŠ€νƒ€μΌμ˜ μ ‘κ·Ό λ°©μ‹μ—μ„œλŠ” μœ„μ ―μ„ λΆ€λͺ¨λ‘œ, λ²„νŠΌμ„ μžμ‹μœΌλ‘œ μƒκ°ν•˜μ§€ μ•ŠλŠ”λ‹€. 였히렀 μœ„μ ―μ€ 객체 일 뿐이며 νŠΉμ • μœ ν˜•μ˜ μœ„μ ―μ΄ μœ„μž„ ν•  μˆ˜μžˆλŠ” μΌμ’…μ˜ μœ ν‹Έλ¦¬ν‹° μ»¬λ ‰μ…˜μ΄λ©° Button은 λ…λ¦½ν˜• 객체이닀.

OLOOλŠ” 생성과 μ΄ˆκΈ°ν™”κ°€ λ°˜λ“œμ‹œ λ™μΌν•œ μž‘μ—…μœΌλ‘œ ν†΅ν•©λ˜μ§€ μ•ŠλŠ” 뢄리 원칙을 더 잘 μ§€μ›ν•œλ‹€.

Simpler Design

De-class-ified

var LoginController = {
	errors: [],
	getUser: function() {
		return document.getElementById( "login_username" ).value;
	},
	getPassword: function() {
		return document.getElementById( "login_password" ).value;
	},
	validateEntry: function(user,pw) {
		user = user || this.getUser();
		pw = pw || this.getPassword();

		if (!(user && pw)) {
			return this.failure( "Please enter a username & password!" );
		}
		else if (pw.length < 5) {
			return this.failure( "Password must be 5+ characters!" );
		}

		// got here? validated!
		return true;
	},
	showDialog: function(title,msg) {
		// display success message to user in dialog
	},
	failure: function(err) {
		this.errors.push( err );
		this.showDialog( "Error", "Login invalid: " + err );
	}
};

OLOO μŠ€νƒ€μΌ μ½”λ“œμ˜ 힘과 행동 μœ„μž„ λ””μžμΈ νŒ¨ν„΄μ˜ νž˜μ€ 우리λ₯Ό λ™μΌν•œ κΈ°λŠ₯을 κ°–μ§€λ§Œ (μƒλ‹Ήνžˆ) 더 λ‹¨μˆœν•œ μ„€κ³„λ‘œ μ΄λˆλ‹€.

Nicer Syntax

ES6λΆ€ν„°λŠ” κ°μ²΄λ‚΄μ—μ„œ 약식 λ©”μ„œλ“œ 선언이 κ°€λŠ₯ν•˜λ‹€.

var LoginController = {
	errors: [],
	getUser() { // Look ma, no `function`!
		// ...
	},
	getPassword() {
		// ...
	}
	// ...
};

Unlexical

var Foo = {
	bar: function(x) {
		if (x < 10) {
			return Foo.bar( x * 2 );
		}
		return x;
	},
	baz: function baz(x) {
		if (x < 10) {
			return baz( x * 2 );
		}
		return x;
	}
};

읡λͺ… ν•¨μˆ˜λ₯Ό λ§Œλ“€μ§€λ§μž.

Introspection

var Foo = { /* .. */ };

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

var b1 = Object.create( Bar );
// relating `Foo` and `Bar` to each other
Foo.isPrototypeOf( Bar ); // true
Object.getPrototypeOf( Bar ) === Foo; // true

// relating `b1` to both `Foo` and `Bar`
Foo.isPrototypeOf( b1 ); // true
Bar.isPrototypeOf( b1 ); // true
Object.getPrototypeOf( b1 ) === Bar; // true

Review (TL;DR)

ν΄λž˜μŠ€μ™€ 상속은 μ†Œν”„νŠΈμ›¨μ–΄ μ•„ν‚€ν…μ²˜μ—μ„œ μ„ νƒν•˜κ±°λ‚˜ μ„ νƒν•˜μ§€ μ•Šμ„ μˆ˜μžˆλŠ” λ””μžμΈ νŒ¨ν„΄μ΄λ‹€. μš°λ¦¬λŠ” μ‹€μ œλ‘œ 맀우 κ°•λ ₯ν•œ νŒ¨ν„΄μ— λŒ€ν•΄ 덜 일반적으둜 μ–ΈκΈ‰λ˜λŠ” 또 λ‹€λ₯Έ 방법 인 행동 μœ„μž„μ΄ μžˆλ‹€.

λ™μž‘ μœ„μž„μ€ 개체λ₯Ό λΆ€λͺ¨ 및 μžμ‹ 클래슀 관계가 μ•„λ‹Œ μ„œλ‘œκ°„μ— μœ„μž„ν•˜λŠ” μ„œλ‘œμ˜ ν”Όμ–΄λ‘œ μ œμ•ˆν•œλ‹€. JavaScript의 Prototype λ©”μ»€λ‹ˆμ¦˜μ€ 맀우 μ„€κ³„λœ νŠΉμ„±μƒ λ™μž‘ μœ„μž„ λ©”μ»€λ‹ˆμ¦˜μ΄λ‹€. OLOO (objects-linked-to-other-objects)λŠ” 클래슀의 좔상화없이 직접 객체λ₯Ό μƒμ„±ν•˜κ³  κ΄€λ ¨μ‹œν‚€λŠ” μ½”λ“œ μŠ€νƒ€μΌμ΄λ‹€. OLOOλŠ” Prototype 기반 행동 μœ„μž„μ„ μ•„μ£Ό μžμ—°μŠ€λŸ½κ²Œ κ΅¬ν˜„ν•  수 μžˆλ‹€.

⚠️ **GitHub.com Fallback** ⚠️