Chapter 6: Behavior Delegation - hochan222/Everything-in-JavaScript GitHub Wiki
5μ₯μ λν μ 리λ‘μ Prototype λ©μ»€λμ¦μ λ€λ₯Έ κ°μ²΄λ₯Ό μ°Έμ‘°νλ νλμ κ°μ²΄μ μ‘΄μ¬νλ λ΄λΆ λ§ν¬μ΄λ€. μΌλ ¨μ κ°μ²΄κ° μ°κ²°μ νλ‘ν νμ 체μΈμ νμ±νλ€.
μννΈμ¨μ΄μμ λͺ¨λΈλ§ν΄μΌνλ μ μ¬ν μμ
[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 μ μ λμμ 볡μ¬λ³Έμ΄ λͺ¨λ μλ€.
μμ± νμλ κ° μΈμ€ν΄μ€κ° μλ ν μμ
μ μννλ λ° νμν λͺ¨λ λμμ 볡μ¬λ³Έμ κ°μ§κ³ μμΌλ―λ‘ μΌλ°μ μΌλ‘ μ΄λ¬ν μΈμ€ν΄μ€μ λ§ μνΈ μμ©νλ€(ν΄λμ€κ° μλλΌ).
λ¨Όμ 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)κ° μμ± λλ λ©μλ μ°Έμ‘°μ λν μμ (νμ€ν¬)μ μ 곡νλλ‘νλ€.
μ΄κ²μ λΆλͺ¨μ μμ ν΄λμ€, μμ, λ€νμ± λ±μ κ°λ
κ³Όλ λ§€μ° λ€λ₯Έ λ§€μ° κ°λ ₯ν λμμΈ ν¨ν΄μ΄λ€.
λΆλͺ¨κ° μμμΌλ‘ νλ¬κ°λ μνμμ κ°μ²΄λ₯Ό μμ§μΌλ‘ ꡬμ±νλ λμ κ°μ²΄λ₯Ό λλν μλ€κ³ μκ°ν μ μλ€.
λ μ΄μμ κ°μ²΄κ° μλ‘μκ² (μλ°©ν₯μΌλ‘) μμλλ μ£ΌκΈ°λ₯Ό λ§λ€ μ μλ€.
Bλ₯Ό Aμ μ°κ²° ν λ€μ Aλ₯Ό Bμ μ°κ²°νλ €κ³ νλ©΄ μ€λ₯κ° λ°μνλ€.
μμ§ κ΅¬νμκ° κ°μ²΄μμ μμ±μ μ‘°ν ν λλ§λ€ ν΄λΉ κ°λ κ²μ¬μ μ±λ₯ μ μ€μ νμλ‘νλ κ²λ³΄λ€ μ€μ μκ°μ ν λ² λ¬΄ν μν μ°Έμ‘°λ₯Ό νμΈ (λ° κ±°λΆ)νλ κ²μ΄ λ μ±λ₯μ΄ μ’λ€λ κ²μ κ΄μ°°νκΈ° λλ¬Έμ νμ©λμ§ μλλ€.
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μμ λ²κ·Έλ‘ νμΈλμλ€.
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();
μ€μ μ΄λ€. λ¨Όμ νλ‘ νΈ μλ μΉ κ°λ°μ μΌλ°μ μΈ μλλ¦¬μ€ μΈ UI μμ ― (λ²νΌ, λλ‘ λ€μ΄ λ±) μμ±μ μ΄ν΄λ³΄μ.
// 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 );
} );
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 );
} );
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λ μμ±κ³Ό μ΄κΈ°νκ° λ°λμ λμΌν μμ μΌλ‘ ν΅ν©λμ§ μλ λΆλ¦¬ μμΉμ λ μ μ§μνλ€.
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 μ€νμΌ μ½λμ νκ³Ό νλ μμ λμμΈ ν¨ν΄μ νμ μ°λ¦¬λ₯Ό λμΌν κΈ°λ₯μ κ°μ§λ§ (μλΉν) λ λ¨μν μ€κ³λ‘ μ΄λλ€.
ES6λΆν°λ κ°μ²΄λ΄μμ μ½μ λ©μλ μ μΈμ΄ κ°λ₯νλ€.
var LoginController = {
errors: [],
getUser() { // Look ma, no `function`!
// ...
},
getPassword() {
// ...
}
// ...
};
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;
}
};
μ΅λͺ ν¨μλ₯Ό λ§λ€μ§λ§μ.
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
ν΄λμ€μ μμμ μννΈμ¨μ΄ μν€ν μ²μμ μ ννκ±°λ μ ννμ§ μμ μμλ λμμΈ ν¨ν΄μ΄λ€. μ°λ¦¬λ μ€μ λ‘ λ§€μ° κ°λ ₯ν ν¨ν΄μ λν΄ λ μΌλ°μ μΌλ‘ μΈκΈλλ λ λ€λ₯Έ λ°©λ² μΈ νλ μμμ΄ μλ€.
λμ μμμ κ°μ²΄λ₯Ό λΆλͺ¨ λ° μμ ν΄λμ€ κ΄κ³κ° μλ μλ‘κ°μ μμνλ μλ‘μ νΌμ΄λ‘ μ μνλ€. JavaScriptμ Prototype λ©μ»€λμ¦μ λ§€μ° μ€κ³λ νΉμ±μ λμ μμ λ©μ»€λμ¦μ΄λ€. OLOO (objects-linked-to-other-objects)λ ν΄λμ€μ μΆμνμμ΄ μ§μ κ°μ²΄λ₯Ό μμ±νκ³ κ΄λ ¨μν€λ μ½λ μ€νμΌμ΄λ€. OLOOλ Prototype κΈ°λ° νλ μμμ μμ£Ό μμ°μ€λ½κ² ꡬνν μ μλ€.