javascript design patterns - Lee-hyuna/33-js-concepts-kr GitHub Wiki
μλ°μ€ν¬λ¦½νΈ λμμΈ ν¨ν΄
μλ¬Έ: JavaScript Design Patterns
μ΄ κΈ°μ¬μμλ μ μ§λ³΄μνκΈ° λ μ’μ JavaScript μ½λλ₯Ό μμ±νλ λ° μ¬μ©ν μμλ λμμΈ ν¨ν΄μ λν΄ μ€λͺ ν©λλ€. JavaScriptμ ν΄λμ€μ κ°μ²΄, νλ‘ν νμ μμ, ν΄λ‘μ λ±κ³Ό κ°μ κ°λ μ κΈ°λ³Έμ μΌλ‘ μ΄ν΄νκ³ μλ€κ³ κ°μ νλ€.
μ΄ κΈ°μ¬λ μ£Όμ μ νΉμ±μΌλ‘ μΈν΄ μ 체μ μΌλ‘ μ€λ«λμ μ½νμ‘μΌλ―λ‘ μΉμ μ λ 립μ μΌλ‘ μ μ§νλ €κ³ λ Έλ ₯νλ€. λ°λΌμ νΉμ λΆλΆ (λλ μ΄ κ²½μ° νΉμ ν¨ν΄)μ κ³¨λΌ μ ννκ³ κ΄μ¬μ΄ μκ±°λ μ λͺ¨λ₯΄λ λΆλΆμ 무μνλ©΄ λλ€.
μ°Έκ³ : μ¬κΈ°μ μ€λͺ λ λͺ¨λ λμμΈ ν¨ν΄μ ꡬννκΈ°μν μμ€ μ½λλ GitHub μ μλ€.
μκ°
μ°λ¦¬λ λ¬Έμ λ₯Ό ν΄κ²°νκΈ° μν μ½λλ₯Ό μμ±νλ€. μ΄λ¬ν λ¬Έμ λ μΌλ°μ μΌλ‘ λ§μ μ μ¬μ μ κ°μ§κ³ μμΌλ©°, μ΄λ₯Ό ν΄κ²°νλ €κ³ ν λ λͺ κ°μ§ 곡ν΅μ μΈ ν¨ν΄μ΄ μλ€. λμμΈ ν¨ν΄μ΄ λ±μ₯νλ μ΄μ μ΄λ€.
λμμΈ ν¨ν΄μ μννΈμ¨μ΄ μ€κ³μμ μΌλ°μ μΌλ‘ λ°μνλ λ¬Έμ μ λν μΌλ°μ μΈ μ¬μ¬μ© μ루μ μννΈμ¨μ΄ 곡νμμ μ¬μ©λλ μ©μ΄μ΄λ€.
λμμΈ ν¨ν΄μ κΈ°λ³Έ κ°λ μ μ²μλΆν° μννΈμ¨μ΄ μμ§λμ΄λ§ μ°μ μμ μ¬μ©λμ΄ μμ§λ§ μ€μ λ‘ κ³΅μνλμ§λ μμλ€. Erich Gamma, Richard Helm, Ralph Johnson, κ·Έλ¦¬κ³ John Vlissidesμν΄ μμ± λ λμμΈ ν¨ν΄ : μ¬μ¬μ© κ°λ₯ν κ°μ²΄ μ§ν₯ μννΈμ¨μ΄μ μμ μννΈμ¨μ΄ μμ§λμ΄λ§μμ 곡μν λ λμμΈ ν¨ν΄ κ°λ μ μΆμ§νλ λ° μ€μν μν μ νλ€. μ΄μ λμμΈ ν¨ν΄μ μννΈμ¨μ΄ κ°λ°μ νμ μμμ΄λ©° μ€λ«λμ μ¬μ©λμ΄ μλ€.
μλ³Έ μ± μλ 23 κ°μ λμμΈ ν¨ν΄μ΄ λμ λμλ€.
λμμΈ ν¨ν΄μ μ¬λ¬ κ°μ§ μ΄μ λ‘ μ μ©νλ€. λμμΈ ν¨ν΄μ μ κ³μμ μ μ¦ λ ν΄κ²° λ°©λ²μ μ μνλ€. λ리 μλ €μ§ λ°©μμΌλ‘ λ¬Έμ λ₯Ό ν΄κ²°νκ³ μ΄λ₯Ό μ μνλ λ° λμμ΄ λ μ κ³ μ΅κ³ μ κ°λ°μμ κ²½νκ³Ό ν΅μ°°λ ₯μ λ°μνλ, κ²¬κ³ ν μ κ·Ό λ°©μμ΄λ€. ν¨ν΄μ λν κ°λ° νλ‘μΈμ€μ μλλ₯Ό ν¬κ² λμ΄λ λμμ μ½λλ₯Όλ³΄λ€ μ¬μ¬μ© κ°λ₯νκ³ μ½κΈ° μ½κ² λ§λ λ€.
κ·Έλ¬λ λμμΈ ν¨ν΄μ κ²°μ½ μμ± λ μ루μ μ΄ μλλ€. μ΄κ²λ€μ λ¨μ§ λ¬Έμ λ₯Ό ν΄κ²°νλ λ°©λ²μ΄λ κΈ°λ²μ μ°λ¦¬μκ² μ 곡ν λΏμ΄λ€.
μ°Έκ³ : μ΄ κΈ°μ¬μμλ μ£Όλ‘ κ°μ²΄ μ§ν₯ κ΄μ κ³Ό νλ JavaScriptμμμ μ μ©μ±μ κ΄ν λμμΈ ν¨ν΄μ λν΄ μ€λͺ νλ€. κ·Έλ κΈ° λλ¬Έμ GoFμ λ§μ ν΄λμ ν¨ν΄μ΄ μλ΅ λ μ μμΌλ©° Addy Osmaniμ Learn JavaScript Design Patterns μ κ°μ μμ€μ μΌλΆ μ΅μ ν¨ν΄ μ΄ ν¬ν¨λ©λλ€. μμ λ μ΄ν΄νκΈ° μ½λλ‘ λ¨μνκ² μ μ§λλ―λ‘ κ° λμμΈ ν¨ν΄μ μ΅μ ν λ ꡬνμ΄ μλλ€.
λμμΈ ν¨ν΄μ λ²μ£Ό
λμμΈ ν¨ν΄μ μΌλ°μ μΌλ‘ μΈ κ°μ§ μ£Όμ κ·Έλ£ΉμΌλ‘ λΆλ₯λ©λλ€.
μ°½μ‘° λμμΈ ν¨ν΄
μ΄λ¦μμ μ μ μλ―μ΄ μ΄λ¬ν ν¨ν΄μ κ°μ²΄ μμ± λ©μ»€λμ¦μ μ²λ¦¬νκΈ°μν κ²μ΄λ€. μ°½μ‘° λμμΈ ν¨ν΄μ κΈ°λ³Έμ μΌλ‘ κ°μ²΄μ μμ± κ³Όμ μ μ μ΄ν¨μΌλ‘μ¨ λ¬Έμ λ₯Ό ν΄κ²°νλ€.
μμ±μ ν¨ν΄, ν©ν 리 ν¨ν΄, νλ‘ν νμ ν¨ν΄ λ° μ±κΈν€ ν¨ν΄ κ³Ό κ°μ ν¨ν΄μ λν΄ μμΈν μ€λͺ νλ€.
ꡬ쑰 μ€κ³ ν¨ν΄
μ΄λ¬ν ν¨ν΄μ ν΄λμ€ λ° κ°μ²΄ ꡬμ±κ³Ό κ΄λ ¨μ΄ μλ€. μ 체 μμ€ν μ μν₯μμ£Όμ§ μκ³ νλ μ΄μμ λΆνμ ꡬ쑰ννκ±°λ μ¬κ΅¬μ± ν μ μλ€. μ¦, κΈ°μ‘΄ κΈ°λ₯μ λ³κ²½νμ§ μκ³ λ μλ‘μ΄ κΈ°λ₯μ μ»μ μ μλ€.
μ΄λν° ν¨ν΄, λ³΅ν© ν¨ν΄, λ°μ½λ μ΄ν° ν¨ν΄, νΌμ¬λ ν¨ν΄, νλΌμ΄μ¨μ΄νΈ ν¨ν΄ λ° νλ‘μ ν¨ν΄ κ³Ό κ°μ ν¨ν΄μ λν΄ μμΈν μ€λͺ νλ€.
νλ λμμΈ ν¨ν΄
μ΄λ¬ν ν¨ν΄μ μ΄ κΈ°μ’ κ°μ²΄ κ°μ ν΅μ μ κ°μ νλ λ° κ΄λ ¨λλ€.
μ± μ μ¬μ¬ ν¨ν΄, 컀맨λ ν¨ν΄, μ΄ν°λ μ΄ν° ν¨ν΄, μ€μ¬μ ν¨ν΄, κ΄μ°°μ ββν¨ν΄, μν ν¨ν΄, μ λ΅ ν¨ν΄ λ° ν νλ¦Ώ ν¨ν΄ κ³Ό κ°μ ν¨ν΄μ λν΄ μμΈν μ€λͺ νλ€.
μμ±μ ν¨ν΄ (Factory Pattern)
ν΄λμ€ κΈ°λ°μ λμμΈ ν¨ν΄μ΄λ€. μμ±μλ ν΄λΉ ν¨μλ‘ μ μλ λ©μλ λ° μμ±μΌλ‘ μ κ°μ²΄λ₯Ό μΈμ€ν΄μ€ννλ λ° μ¬μ©ν μ μλ νΉμ ν¨μμ΄λ€.
κ³ μ μ μΈ λμμΈ ν¨ν΄μ μλλ€. μ€μ λ‘, λλΆλΆμ κ°μ²΄ μ§ν₯ μΈμ΄μ ν¨ν΄μ΄λΌκΈ° λ³΄λ¨ κΈ°λ³Έμ μΈ μΈμ΄ ꡬμ±μ κ°κΉλ€. κ·Έλ¬λ JavaScriptμμλ μμ±μ ν¨μλ "class" μ μ μμ΄λ κ°μ²΄λ₯Ό μ¦μμμ λ§λ€ μ μλ€. λ°λΌμ μ΄ λ¨μν νλμ ν¨ν΄μ΄ λ€λ₯Έ ν¨ν΄λ€μ κΈ°λ°μ΄ λλ€κ³ μκ°νλ€.
μμ±μ ν¨ν΄μ μ£Όμ΄μ§ μ’ λ₯μ μ κ°μ²΄λ₯Ό μμ±νκΈ° μν΄ JavaScriptμμ κ°μ₯ μΌλ°μ μΌλ‘ μ¬μ©λλ ν¨ν΄ μ€ νλμ΄λ€.
μ΄ μμ μμ, name
μ specialAbility
μ κ°μ μμ±κ³Ό getDetails
μ κ°μ λ©μλλ₯Ό κ°μ§ Hero
ν΄λμ€λ₯Ό μ μνλ€. κ·Έλ° λ€μ κ° μμ±μ κ°μ μΈμλ‘ μ λ¬νλ new
ν€μλλ‘ μμ±μ λ©μλλ₯Ό νΈμΆνμ¬ κ°μ²΄ IronMan
μ μΈμ€ν΄μ€ννλ€.
// traditional Function-based syntax
function Hero(name, specialAbility) {
// setting property values
this.name = name;
this.specialAbility = specialAbility;
// declaring a method on the object
this.getDetails = function() {
return this.name + ' can ' + this.specialAbility;
};
}
// ES6 Class syntax
class Hero {
constructor(name, specialAbility) {
// setting property values
this._name = name;
this._specialAbility = specialAbility;
// declaring a method on the object
this.getDetails = function() {
return `${this._name} can ${this._specialAbility}`;
};
}
}
// creating new instances of Hero
const IronMan = new Hero('Iron Man', 'fly');
console.log(IronMan.getDetails()); // Iron Man can fly
ν©ν 리 ν¨ν΄ (Factory Pattern)
ν©ν 리 ν¨ν΄μ λ λ€λ₯Έ ν΄λμ€ κΈ°λ° μμ± ν¨ν΄μ΄λ€. κ°μ²΄ μΈμ€ν΄μ€νμ μ± μμ νμ ν΄λμ€μ μμνλ λ²μ© μΈν°νμ΄μ€λ₯Ό μ 곡νλ€.
μ΄ ν¨ν΄μ μλ‘ λ€λ₯΄μ§λ§ λΉμ·ν νΉμ±μ΄ λ§μ κ°μ²΄ 컬λ μ μ κ΄λ¦¬νκ±°λ μ‘°μν΄μΌ ν λ μμ£Ό μ¬μ©λλ€.
μ΄ μμμλ 맀κ°λ³μλ₯Ό μ·¨νλ λ©μλλ₯Ό κ°μ§ BallFactory
λΌλ ν©ν 리 ν΄λμ€λ₯Ό λ§λ€κ³ , 맀κ°λ³μμ λ°λΌ κ°μ²΄ μΈμ€ν΄μ€ν μ±
μμ κ° ν΄λμ€μ μμνλ€. νμ
맀κ°λ³μκ° "football"
λλ "soccer"
μΈ κ²½μ°, κ°μ²΄ μΈμ€ν΄μ€νλ Football
ν΄λμ€μμ μ²λ¦¬νμ§λ§, "basketball"
κ°μ²΄ μΈμ€ν΄μ€νλ Basketball
ν΄λμ€μμ μ²λ¦¬νλ€.
class BallFactory {
constructor() {
this.createBall = function(type) {
let ball;
if (type === 'football' || type === 'soccer') ball = new Football();
else if (type === 'basketball') ball = new Basketball();
ball.roll = function() {
return `The ${this._type} is rolling.`;
};
return ball;
};
}
}
class Football {
constructor() {
this._type = 'football';
this.kick = function() {
return 'You kicked the football.';
};
}
}
class Basketball {
constructor() {
this._type = 'basketball';
this.bounce = function() {
return 'You bounced the basketball.';
};
}
}
// creating objects
const factory = new BallFactory();
const myFootball = factory.createBall('football');
const myBasketball = factory.createBall('basketball');
console.log(myFootball.roll()); // The football is rolling.
console.log(myBasketball.roll()); // The basketball is rolling.
console.log(myFootball.kick()); // You kicked the football.
console.log(myBasketball.bounce()); // You bounced the basketball.
νλ‘ν νμ ν¨ν΄ (Prototype Pattern)
μ΄ ν¨ν΄μ κ³ μ μ μΈ κ°μ²΄μ§ν₯ μμ λμ νλ‘ν νμ μμμ μ¬μ©νλ―λ‘ JavaScriptμ νΉν μ€μνλ€. λ°λΌμ JavaScriptμ κ°μ μ λ°λ₯΄λ©° κΈ°λ³Έ μ§μμ μ 곡νλ€.
μ΄ μμ μλ JavaScriptμ Object.create
κΈ°λ₯μ μ¬μ©νμ¬ λ€λ₯Έ κ°μ²΄ myCar
λ₯Ό μμ±νκ³ μ κ°μ²΄μ μΆκ° μμ± owner
λ₯Ό μ μνκΈ° μν΄ νλ‘ν νμ
μΌλ‘ μ¬μ©νλ car
κ°μ²΄κ° μλ€.
// using Object.create as was recommended by ES5 standard
const car = {
noOfWheels: 4,
start() {
return 'started';
},
stop() {
return 'stopped';
},
};
// Object.create(proto[, propertiesObject])
const myCar = Object.create(car, { owner: { value: 'John' } });
console.log(myCar.__proto__ === car); // true
μ±κΈν€ ν¨ν΄ (Singleton Pattern)
μ±κΈν€μ νλμ ν΄λμ€μ μΈμ€ν΄μ€λ§ μ‘΄μ¬ν μ μλ νΉλ³ν λμμΈ ν¨ν΄μ΄λ€. μ±κΈν€ ν΄λμ€μ μΈμ€ν΄μ€κ° μμΌλ©΄ μ μΈμ€ν΄μ€κ° λ§λ€μ΄μ§κ³ λ°νλμ§λ§ μΈμ€ν΄μ€κ° μ΄λ―Έ μ‘΄μ¬νλ©΄ κΈ°μ‘΄ μΈμ€ν΄μ€μ λν μ°Έμ‘°κ° λ°νλλ€.
μλ²½ν μ€μ μ¬λ‘λ MongoDBμ μ λͺ
ν Node.js ODM λΌμ΄λΈλ¬λ¦¬μΈ mongoose
μ΄λ€. mongoose
λ μ±κΈ ν€ ν¨ν΄μ μ¬μ©νλ€.
μ΄ μμ μλ μ±κΈν€ ν΄λμ€μΈ Database
κ° μλ€. λ¨Όμ new
μ°μ°μλ₯Ό μ¬μ©νμ¬ Database
ν΄λμ€ μμ±μλ₯Ό νΈμΆν΄ κ°μ²΄ mongo
λ₯Ό λ§λ λ€. μ΄λ²μλ κ°μ²΄κ° μκΈ° λλ¬Έμ κ°μ²΄κ° μΈμ€ν΄μ€ν λλ€. λ λ²μ§Έλ‘ mysql
κ°μ²΄λ₯Ό λ§λ€ λ, μ κ°μ²΄κ° μΈμ€ν΄μ€νλμ§ μκ³ μ΄μ μ μΈμ€ν΄μ€ν λ κ°μ²΄, μ¦ mongo
κ°μ²΄μ λν μ°Έμ‘°κ° λ°νλλ€.
class Database {
constructor(data) {
if (Database.exists) {
return Database.instance;
}
this._data = data;
Database.instance = this;
Database.exists = true;
return this;
}
getData() {
return this._data;
}
setData(data) {
this._data = data;
}
}
// usage
const mongo = new Database('mongo');
console.log(mongo.getData()); // mongo
const mysql = new Database('mysql');
console.log(mysql.getData()); // mongo
μ΄λν° ν¨ν΄ (Adapter Pattern)
μ΄λν° ν¨ν΄μ ν ν΄λμ€μ μΈν°νμ΄μ€κ° λ€λ₯Έ ν΄λμ€μ μΈν°νμ΄μ€λ‘ λ³νλλ ꡬ쑰μ ν¨ν΄μ΄λ€. μ΄ ν¨ν΄μ μ¬μ©νλ©΄ νΈνλμ§ μλ μΈν°νμ΄μ€ λλ¬Έμ λ€λ₯Έ λ°©λ²μΌλ‘λ λΆκ°λ₯νλ ν΄λμ€κ° ν¨κ» μλνλλ‘ νλ€.
μ΄ ν¨ν΄μ μ’ μ’ κΈ°μ‘΄μ λ€λ₯Έ μ€λλ APIκ° μ¬μ ν μλ ν μ μλλ‘, μλ‘μ΄ λ¦¬ν©ν λ§ λ APIμ λν wrapperλ₯Ό μμ±νλ λ° μ¬μ©λλ€. μ΄λ μΌλ°μ μΌλ‘ μλ‘μ΄ κ΅¬ν λλ μ½λ 리ν©ν λ§ (μ±λ₯ ν₯μκ³Ό κ°μ μ΄μ λ‘ μν)μΌλ‘ μΈν΄ λ€λ₯Έ κ³΅μ© APIκ° μμ±λλ λ°λ©΄, μμ€ν μ λ€λ₯Έ λΆλΆμ μ¬μ ν ββκΈ°μ‘΄ APIλ₯Ό μ¬μ©νκ³ ν¨κ» μλνλλ‘ μ‘°μ λμ΄μΌνλ κ²½μ°μ μνλλ€.
μ΄ μμ μλ μ΄μ API (OldCalculator
ν΄λμ€)μ μ API (NewCalculator
ν΄λμ€)κ° μλ€. OldCalculator
ν΄λμ€λ λ§μ
κ³Ό λΊμ
μμν operation
λ©μλλ₯Ό μ 곡νλ λ°λ©΄ NewCalculator
λ λ§μ
κ³Ό λΊμ
μ μν λ³λμ λ©μλλ₯Ό μ 곡νλ€. Adapter ν΄λμ€ CalcAdapter
λ NewCalculator
λ₯Ό κ°μΈμ(wrap) operation
λ©μλλ₯Ό κ³΅μ© APIμ μΆκ°νλ λμμ μ체 λ§μ
λ° λΊμ
ꡬνμ μ¬μ©νλ€.
// old interface
class OldCalculator {
constructor() {
this.operations = function(term1, term2, operation) {
switch (operation) {
case 'add':
return term1 + term2;
case 'sub':
return term1 - term2;
default:
return NaN;
}
};
}
}
// new interface
class NewCalculator {
constructor() {
this.add = function(term1, term2) {
return term1 + term2;
};
this.sub = function(term1, term2) {
return term1 - term2;
};
}
}
// Adapter Class
class CalcAdapter {
constructor() {
const newCalc = new NewCalculator();
this.operations = function(term1, term2, operation) {
switch (operation) {
case 'add':
// using the new implementation under the hood
return newCalc.add(term1, term2);
case 'sub':
return newCalc.sub(term1, term2);
default:
return NaN;
}
};
}
}
// usage
const oldCalc = new OldCalculator();
console.log(oldCalc.operations(10, 5, 'add')); // 15
const newCalc = new NewCalculator();
console.log(newCalc.add(10, 5)); // 15
const adaptedCalc = new CalcAdapter();
console.log(adaptedCalc.operations(10, 5, 'add')); // 15;
λ³΅ν© ν¨ν΄ (Composite pattern)
νΈλ¦¬μ κ°μ κ΅¬μ‘°λ‘ κ°μ²΄λ₯Ό ꡬμ±νμ¬, μ 체 λΆλΆμ κ³μΈ΅ ꡬ쑰λ₯Ό λνλ΄λ ꡬ쑰 μ€κ³ ν¨ν΄μ΄λ€. μ΄ ν¨ν΄μμ νΈλ¦¬μ κ°μ ꡬ쑰μ κ° λ Έλλ κ°λ³ κ°μ²΄μ΄κ±°λ ꡬμ±λ κ°μ²΄ λͺ¨μ μΌ μ μλ€. μ΄μ¨λ κ° λ Έλλ κ· μΌνκ² μ²λ¦¬λλ€.
μ΄ ν¨ν΄μ μκ°ννλ κ²μ μ½κ° 볡μ‘νλ€. μ΄κ²μ μ΄ν΄νλ κ°μ₯ μ¬μ΄ λ°©λ²μ λ€λ¨κ³ λ©λ΄μ μλ₯Ό 보λ κ²μ΄λ€. κ° λ Έλλ μμμΌλ‘ μ¬λ¬ μ΅μ μ΄ μλ λ³κ°μ μ΅μ μΌ μλ μκ³ λ©λ΄ μμ²΄μΌ μ μλ€. μμμ΄ μλ λ Έλ μ»΄ν¬λνΈλ λ³΅ν© μ»΄ν¬λνΈ(composite component)μ΄κ³ μμμ΄ μλ λ Έλ μ»΄ν¬λνΈλ 리ν μ»΄ν¬λνΈ(leaft component)μ΄λ€.
μ΄ μμ μμλ νμν κ³΅ν΅ κΈ°λ₯λ§μ ꡬννκ³ λ€λ₯Έ λ©μλλ₯Ό μΆμννλ Component
μ κΈ°λ³Έ ν΄λμ€λ₯Ό μμ±νλ€. κΈ°λ³Έ ν΄λμ€μλ μ¬κ·λ₯Ό μ¬μ©νμ¬ νμ ν΄λμ€λ‘ λ§λ λ³΅ν© νΈλ¦¬ ꡬ쑰λ₯Ό ν΅κ³Όνλ static λ©μλλ μλ€. κ·Έλ° λ€μ κΈ°λ³Έ ν΄λμ€λ₯Ό νμ₯νλ λ κ°μ μλΈ ν΄λμ€μΈ μμμ΄ μλ Leaf
μ μμμ κ°μ§ μ μλ Composite
λ₯Ό λ§λ λ€. λ κ°μ μλΈ ν΄λμ€λ λ³΅ν© κ΅¬μ‘° (μ΄ κ²½μ° νΈλ¦¬)λ₯Ό μμ±νλ λ° μ¬μ©λλ€.
class Component {
constructor(name) {
this._name = name;
}
getNodeName() {
return this._name;
}
// abstract methods that need to be overridden
getType() {}
addChild(component) {}
removeChildByName(componentName) {}
removeChildByIndex(index) {}
getChildByName(componentName) {}
getChildByIndex(index) {}
noOfChildren() {}
static logTreeStructure(root) {
let treeStructure = '';
function traverse(node, indent = 0) {
treeStructure += `${'--'.repeat(indent)}${node.getNodeName()}\n`;
indent++;
for (let i = 0, length = node.noOfChildren(); i < length; i++) {
traverse(node.getChildByIndex(i), indent);
}
}
traverse(root);
return treeStructure;
}
}
class Leaf extends Component {
constructor(name) {
super(name);
this._type = 'Leaf Node';
}
getType() {
return this._type;
}
noOfChildren() {
return 0;
}
}
class Composite extends Component {
constructor(name) {
super(name);
this._type = 'Composite Node';
this._children = [];
}
getType() {
return this._type;
}
addChild(component) {
this._children = [...this._children, component];
}
removeChildByName(componentName) {
this._children = [...this._children].filter(component => component.getNodeName() !== componentName);
}
removeChildByIndex(index) {
this._children = [...this._children.slice(0, index), ...this._children.slice(index + 1)];
}
getChildByName(componentName) {
return this._children.find(component => component.name === componentName);
}
getChildByIndex(index) {
return this._children[index];
}
noOfChildren() {
return this._children.length;
}
}
// usage
const tree = new Composite('root');
tree.addChild(new Leaf('left'));
const right = new Composite('right');
tree.addChild(right);
right.addChild(new Leaf('right-left'));
const rightMid = new Composite('right-middle');
right.addChild(rightMid);
right.addChild(new Leaf('right-right'));
rightMid.addChild(new Leaf('left-end'));
rightMid.addChild(new Leaf('right-end'));
// log
console.log(Component.logTreeStructure(tree));
/*
root
--left
--right
----right-left
----right-middle
------left-end
------right-end
----right-right
*/
λ°μ½λ μ΄ν° ν¨ν΄ (Decorator Pattern)
μ΄ ν¨ν΄μ κΈ°μ‘΄ ν΄λμ€μ λμ λλ κΈ°λ₯μ λμ μΌλ‘ μΆκ°νλ κ²μ μ΄μ μ λ ꡬ쑰 μ€κ³ ν¨ν΄μ΄λ€. ν΄λμ€ μμμ λλ€λ₯Έ λμμ΄λ€.
JavaScriptλ₯Ό μ¬μ©νλ©΄ κ°μ²΄μ λ©μλμ μμ±μ λμ μΌλ‘ μΆκ°ν μ μκΈ° λλ¬Έμ, JavaScriptμμ λ°μ½λ μ΄ν° μ ν λμμ λ§€μ° μ½κ² ꡬνν μ μλ€. κ°μ₯ κ°λ¨ν μ κ·Όλ°©μμ κ°μ²΄μ μμ±μ μΆκ°νλ κ²μ΄μ§λ§, μ¬μ¬μ© ν μλ μμ κ²μ΄λ€.
μ€μ λ‘, JavaScript μΈμ΄μ λ°μ½λ μ΄ν°λ₯Ό μΆκ°νλΌλ μ μμ΄ μλ€. JavaScriptμ λ°μ½λ μ΄ν°μ λν Addy Osmaniμ κ²μλ¬Ό μ μ΄ν΄λ³΄μ .
μ μμ μ체 μ λν΄ μ½μΌλ €λ©΄ μ¬κΈ°.
μ΄ μμμλ Book
ν΄λμ€λ₯Ό λ§λ λ€. λν book κ°μ²΄λ₯Ό μΈμλ‘ λ°κ³ "κΎΈλ©°μ§(decorated)" book
κ°μ²΄λ₯Ό λ°ννλ λ κ°μ λ°μ½λ μ΄ν° ν¨μλ₯Ό μΆκ°λ‘ λ§λ λ€. νλμ μλ‘μ΄ μμ±κ³Ό νλμ μλ‘μ΄ ν¨μλ₯Ό μΆκ°νλ giftWrap
κ³Ό νλμ μλ‘μ΄ μμ±μ μΆκ°νκ³ νλμ κΈ°μ‘΄ μμ±μ κ°μ νΈμ§νλ hardbindBook
μ΄λ€.
class Book {
constructor(title, author, price) {
this._title = title;
this._author = author;
this.price = price;
}
getDetails() {
return `${this._title} by ${this._author}`;
}
}
// decorator 1
function giftWrap(book) {
book.isGiftWrapped = true;
book.unwrap = function() {
return `Unwrapped ${book.getDetails()}`;
};
return book;
}
// decorator 2
function hardbindBook(book) {
book.isHardbound = true;
book.price += 5;
return book;
}
// usage
const alchemist = giftWrap(new Book('The Alchemist', 'Paulo Coelho', 10));
console.log(alchemist.isGiftWrapped); // true
console.log(alchemist.unwrap()); // 'Unwrapped The Alchemist by Paulo Coelho'
const inferno = hardbindBook(new Book('Inferno', 'Dan Brown', 15));
console.log(inferno.isHardbound); // true
console.log(inferno.price); // 20
νΌμ¬λ ν¨ν΄ (Facade Pattern)
νΌμ¬λ ν¨ν΄μ JavaScript λΌμ΄λΈλ¬λ¦¬μμ λ리 μ¬μ©λλ ꡬ쑰 μ€κ³ ν¨ν΄μ΄λ€. νΌμ¬λ ν¨ν΄μ νμ μμ€ν μ΄λ νμ ν΄λμ€μ 볡μ‘μ±μΌλ‘λΆν° λ²μ΄λ μ¬μ© νΈμμ±μ μν΄ ν΅ν©λκ³ λ¨μν 곡곡 μ§ν₯ μΈν°νμ΄μ€λ₯Ό μ 곡νκΈ° μν΄ μ¬μ©λλ€.
μ΄ ν¨ν΄μ μ¬μ©μ jQueryμ κ°μ λΌμ΄λΈλ¬λ¦¬μμ λ§€μ° μΌλ°μ μ΄λ€.
μ΄ μμμλ ComplaintRegistry
ν΄λμ€λ₯Ό μ¬μ©νμ¬ κ³΅κ° APIλ₯Ό λ§λ λ€. ν΄λΌμ΄μΈνΈκ° μ¬μ©ν νλμ λ©μλ, μ¦ registerComplaint
λ§ λ
ΈμΆνλ€. registerComplaint
λ λ΄λΆμ μΌλ‘ type μΈμμ λ°λΌ ProductComplaint
λλ ServiceComplaint
μ€ νλμ νμν κ°μ²΄λ₯Ό μΈμ€ν΄μ€ν μ²λ¦¬νλ€. λν κ³ μ ID μμ±, λΆλ§ μ¬νμ λ©λͺ¨λ¦¬μ μ μ₯νλ λ±μ λ€λ₯Έ λͺ¨λ 볡μ‘ν κΈ°λ₯λ μ²λ¦¬νλ€. κ·Έλ¬λ μ΄λ¬ν λͺ¨λ 볡μ‘μ±μ νΌμ¬λ ν¨ν΄μ μ¬μ©νμ¬ μ¨κ²¨μ Έ μλ€.
let currentId = 0;
class ComplaintRegistry {
registerComplaint(customer, type, details) {
const id = ComplaintRegistry._uniqueIdGenerator();
let registry;
if (type === 'service') {
registry = new ServiceComplaints();
} else {
registry = new ProductComplaints();
}
return registry.addComplaint({ id, customer, details });
}
static _uniqueIdGenerator() {
return ++currentId;
}
}
class Complaints {
constructor() {
this.complaints = [];
}
addComplaint(complaint) {
this.complaints.push(complaint);
return this.replyMessage(complaint);
}
getComplaint(id) {
return this.complaints.find(complaint => complaint.id === id);
}
replyMessage(complaint) {}
}
class ProductComplaints extends Complaints {
constructor() {
super();
if (ProductComplaints.exists) {
return ProductComplaints.instance;
}
ProductComplaints.instance = this;
ProductComplaints.exists = true;
return this;
}
replyMessage({ id, customer, details }) {
return `Complaint No. ${id} reported by ${customer} regarding ${details} have been filed with the Products Complaint Department. Replacement/Repairment of the product as per terms and conditions will be carried out soon.`;
}
}
class ServiceComplaints extends Complaints {
constructor() {
super();
if (ServiceComplaints.exists) {
return ServiceComplaints.instance;
}
ServiceComplaints.instance = this;
ServiceComplaints.exists = true;
return this;
}
replyMessage({ id, customer, details }) {
return `Complaint No. ${id} reported by ${customer} regarding ${details} have been filed with the Service Complaint Department. The issue will be resolved or the purchase will be refunded as per terms and conditions.`;
}
}
// usage
const registry = new ComplaintRegistry();
const reportService = registry.registerComplaint('Martha', 'service', 'availability');
// 'Complaint No. 1 reported by Martha regarding availability have been filed with the Service Complaint Department. The issue will be resolved or the purchase will be refunded as per terms and conditions.'
const reportProduct = registry.registerComplaint('Jane', 'product', 'faded color');
// 'Complaint No. 2 reported by Jane regarding faded color have been filed with the Products Complaint Department. Replacement/Repairment of the product as per terms and conditions will be carried out soon.'
νλΌμ΄μ¨μ΄νΈ ν¨ν΄ (Flyweight Pattern)
νλΌμ΄μ¨μ΄νΈ ν¨ν΄μ μΈλ°ν κ°μ²΄(fine-grained objects)λ₯Ό ν΅ν΄ν¨μ¨μ μΈ λ°μ΄ν° 곡μ μ μ€μ μ λ ꡬ쑰 μ€κ³ ν¨ν΄μ λλ€. ν¨μ¨μ± λ° λ©λͺ¨λ¦¬ μ μ½ λͺ©μ μΌλ‘ μ¬μ©λλ€.
μ΄ ν¨ν΄μ λͺ¨λ μ’ λ₯μ μΊμ± λͺ©μ μΌλ‘ μ¬μ©ν μ μλ€. μ€μ λ‘ μ΅μ λΈλΌμ°μ λ νλΌμ΄μ¨μ΄νΈ ν¨ν΄μ λ³νμ μ¬μ©νμ¬ λμΌν μ΄λ―Έμ§λ₯Ό λ λ²λ‘λνμ§ λͺ»νκ² νλ€.
μ΄ μμμλ μμ΄μ€ν¬λ¦Ό λ§μ κ΄ν λ°μ΄ν°λ₯Ό 곡μ νκΈ°μν μΈλ°ν νλΌμ΄κΈ ν΄λμ€ μ IcecreamFactory
ν΄λΉ νλΌμ΄κΈ κ°μ²΄λ₯Ό λ§λλ ν©ν 리 ν΄λμ€ λ₯Ό λ§λλλ€. λ©λͺ¨λ¦¬ μ μ½μ μν΄ λμΌν κ°μ²΄κ° λ λ² μΈμ€ν΄μ€νλλ©΄ κ°μ²΄κ° μ¬νμ©λ©λλ€. νλΌμ΄κΈ ꡬνμ κ°λ¨ν μμ
λλ€.
μ΄ μμμλ μμ΄μ€ν¬λ¦Ό λ§μ κ΄ν λ°μ΄ν°λ₯Ό 곡μ νκΈ°μν μΈλ°ν νλΌμ΄μ¨μ΄νΈ ν΄λμ€ Icecream
κ³Ό ν΄λΉ νλΌμ΄μ¨μ΄νΈ κ°μ²΄λ₯Ό λ§λ€κΈ°μν ν©ν 리 ν΄λμ€ IcecreamFactory
λ₯Ό λ§λ λ€. λ©λͺ¨λ¦¬ μ μ½μ μν΄ λμΌν κ°μ²΄κ° λ λ² μΈμ€ν΄μ€νλλ©΄ κ°μ²΄κ° μ¬νμ©λλ€. νλΌμ΄μ¨μ΄νΈ ν¨ν΄ ꡬνμ κ°λ¨ν μμ΄λ€.
// flyweight class
class Icecream {
constructor(flavour, price) {
this.flavour = flavour;
this.price = price;
}
}
// factory for flyweight objects
class IcecreamFactory {
constructor() {
this._icecreams = [];
}
createIcecream(flavour, price) {
let icecream = this.getIcecream(flavour);
if (icecream) {
return icecream;
} else {
const newIcecream = new Icecream(flavour, price);
this._icecreams.push(newIcecream);
return newIcecream;
}
}
getIcecream(flavour) {
return this._icecreams.find(icecream => icecream.flavour === flavour);
}
}
// usage
const factory = new IcecreamFactory();
const chocoVanilla = factory.createIcecream('chocolate and vanilla', 15);
const vanillaChoco = factory.createIcecream('chocolate and vanilla', 15);
// reference to the same object
console.log(chocoVanilla === vanillaChoco); // true
νλ‘μ ν¨ν΄ (Proxy Pattern)
νλ‘μ ν¨ν΄λ μ΄λ¦μ²λΌ λμνλ ꡬ쑰μ λμμΈ ν¨ν΄μ λλ€. λμ κ°μ²΄λ₯Ό λλ€λ₯Έ κ°μ²΄κ° μ κ·Όν λ, μ κ·Όμ ν΅μ νλ λ리μ μν μ νλ€.
μΌλ°μ μΌλ‘ λμ κ°μ²΄κ° μ μ½μ λ°κ³ λͺ¨λ μ± μμ ν¨μ¨μ μΌλ‘ μ²λ¦¬νμ§ λͺ»ν μμλ μν©μμ μ¬μ©λλ€. μ΄ κ²½μ° νλ‘μλ μΌλ°μ μΌλ‘ ν΄λΌμ΄μΈνΈμ λμΌν μΈν°νμ΄μ€λ₯Ό μ 곡νκ³ κ³Όλν μλ ₯μ νΌνκΈ° μν΄ λμ κ°μ²΄μ λν μ μ΄λ μ κ·Όμ μ§μνλ κ°μ μ μΈ μμ€μ μΆκ°νλ€.
νλ‘μ ν¨ν΄μ λ€νΈμν¬ μμ²μ΄ λ§μ μ ν리μΌμ΄μ μΌλ‘ μμ ν λ λΆνμνκ±°λ μ€λ³΅ λ λ€νΈμν¬ μμ²μ νΌνλ λ° λ§€μ° μ μ©νλ€.
μ΄ μμμλ Proxy λ° Reflectμ λ κ°μ§ μλ‘μ΄ ES6 κΈ°λ₯μ μ¬μ© ν©λλ€. Proxy κ°μ²΄λ JavaScript κ°μ²΄μ κΈ°λ³Έ μμ
μ λν μ¬μ©μ μ§μ λμμ μ μνλ λ° μ¬μ©λ©λλ€ (ν¨μ λ° λ°°μ΄λ JavaScriptμ κ°μ²΄ μ). Proxy κ°μ²΄λ Proxy
κ°μ²΄ λ₯Ό λ§λλ λ° μ¬μ©ν μμλ μμ±μ λ©μλμ΄κ³ , νλ‘μλ₯Ό ν target
κ°μ²΄μ νμν μ¬μ©μ μ§μ μ μ μν handler
κ°μ²΄λ₯Ό μΈμλ‘ λ°λλ€. handler
κ°μ²΄λ μ¬μ©μ μ μ λμμ μΆκ°νλ λ° μ¬μ©λλ get
, set
, has
, apply
κ°μ μΌλΆ νΈλ© κΈ°λ₯μ μ μ ν μ μλ€. λ°λ©΄μ, Reflect
λ Proxyμ handler κ°μ²΄κ° μ§μνλ μ μ¬ν λ©μλλ₯Ό μ€μ€λ‘ static λ©μλλ‘ μ 곡νλ λ΄μ₯ κ°μ²΄μ΄λ€. μμ±μκ° μλλ€. static λ©μλλ κ°λ‘μ±κΈ° κ°λ₯ν μλ°μ€ν¬λ¦½νΈ μμ
μ μ¬μ©λλ€.
μ΄μ λ€νΈμν¬ μμ² κ°μ ν¨μλ₯Ό λ§λ λ€. μ°λ¦¬λ κ·Έκ²μ networkFetch
λ‘ λͺ
λͺ
νλ€. urlμ μΈμλ‘ λ°μ κ·Έμ λ°λΌ μλ΅νλ€. μΊμλ₯Ό μ¬μ©ν μμλ κ²½μ°μλ§ λ€νΈμν¬μμ μλ΅μ λ°λ νλ‘μλ₯Ό ꡬννλ €κ³ νλ€. κ·Έλ μ§ μμΌλ©΄ μΊμμμ μλ΅μ λ°ννλ€.
μΊμ μ μ λ³μλ μΊμ λ μλ΅μ μ μ₯ν©λλ€. target
μΌλ‘ μλ networkFetch
λ₯Ό μ¬μ©νμ¬ proxiedNetworkFetch
λΌλ νλ‘μλ₯Ό μμ±νκ³ handler
κ°μ²΄μμ apply λ©μλλ₯Ό μ¬μ©νμ¬ ν¨μ νΈμΆμ νλ‘μ νλ€. apply λ©μλλ target
κ°μ²΄ μ체μ μ λ¬λλ€. thisArg
κ°κ³Ό args
λ λ°°μ΄κ³Ό κ°μ κ΅¬μ‘°λ‘ μ λ¬λλ€.
μ λ¬ λ url μΈμκ° μΊμμ μλμ§ νμΈνλ€. μΊμμ μ‘΄μ¬νλ©΄ μλ λμ ν¨μλ₯Ό νΈμΆνμ§ μκ³ μλ΅μ λ°ννλ€. κ·Έλ μ§ μμΌλ©΄, μ°λ¦¬λ Reflect.apply
λ©μλλ₯Ό μ¬μ©νμ¬ thisArg
μ args
λ₯Ό μ λ¬νλ©° target
ν¨μλ₯Ό νΈμΆνλ€.
// Target
function networkFetch(url) {
return `${url} - Response from network`;
}
// Proxy
// ES6 Proxy API = new Proxy(target, handler);
const cache = [];
const proxiedNetworkFetch = new Proxy(networkFetch, {
apply(target, thisArg, args) {
const urlParam = args[0];
if (cache.includes(urlParam)) {
return `${urlParam} - Response from cache`;
} else {
cache.push(urlParam);
return Reflect.apply(target, thisArg, args);
}
},
});
// usage
console.log(proxiedNetworkFetch('dogPic.jpg')); // 'dogPic.jpg - Response from network'
console.log(proxiedNetworkFetch('dogPic.jpg')); // 'dogPic.jpg - Response from cache'
μ± μ μ¬μ¬ ν¨ν΄ (Chain of Responsibility Pattern)
μ΄κ²μ λμ¨νκ² μ°κ²°λ κ°μ²΄ 체μΈμ μ 곡νλ νλ λμμΈ ν¨ν΄μ΄λ€. μ΄λ¬ν κ° κ°μ²΄λ ν΄λΌμ΄μΈνΈ μμ²μ λ°λΌ μλνκ±°λ μ²λ¦¬νλλ‘ μ νν μ μλ€.
μ± μ μ¬μ¬ ν¨ν΄μ μ’μ μλ μ΄λ²€νΈκ° μΌλ ¨μ μ€μ²© λ DOM μμλ₯Ό ν΅ν΄ μ νλλ DOMμμμ μ΄λ²€νΈ λ²λΈλ§(bubbling)μΈλ°, κ·Έ μ€ νλλ μ΄λ²€νΈλ₯Ό μμ νκ³ μ΄λ²€νΈμ λν΄ μννκΈ° μν΄ "μ΄λ²€νΈ 리μ€λ"κ° μ²¨λΆλμ΄μμ μ μλ€.
μ΄ μμ μμ, μ°λ¦¬λ CumulativeSum
ν΄λμ€λ₯Ό μμ±νλλ°, μ΄ ν΄λμ€λ μ΅μ
μΌλ‘ initialValue
λ₯Ό λ°μ μΈμ€ν΄μ€ν ν μ μλ€. μ λ¬ λ κ°μ κ°μ²΄μ sum
μμ±μ μΆκ°νκ³ add
λ©μλ νΈμΆμ 체μΈμ νμ©νκΈ° μν΄ object
μ체λ₯Ό λ°ννλ add
λ©μλκ° μλ€.
μ± μ μ¬μ¬ ν¨ν΄μ jQueryμμλ λ³Ό μμλ μΌλ°μ μΈ ν¨ν΄μΌλ‘, jQuery κ°μ²΄μ λν κ±°μ λͺ¨λ λ©μλ νΈμΆμ΄ jQuery κ°μ²΄λ₯Ό λ°ννλ―λ‘ λ©μλ νΈμΆμ ν¨κ» μ°κ²°ν μ μμ΅λλ€.
class CumulativeSum {
constructor(intialValue = 0) {
this.sum = intialValue;
}
add(value) {
this.sum += value;
return this;
}
}
// usage
const sum1 = new CumulativeSum();
console.log(sum1.add(10).add(2).add(50).sum); // 62
const sum2 = new CumulativeSum(10);
console.log(sum2.add(10).add(20).add(5).sum); // 45
컀맨λ ν¨ν΄ (Command Pattern)
컀맨λ ν¨ν΄μ λμ λλ λμμ κ°μ²΄λ‘ μΊ‘μννλ κ²μ λͺ©νλ‘νλ νλ λμμΈ ν¨ν΄μ΄λ€. μ΄ ν¨ν΄μ μ¬μ©νλ©΄ μμ μ μμ²νκ±°λ μ€μ ꡬνμ μ²λ¦¬νκ±°λ μ²λ¦¬νλ κ°μ²΄μμ λ©μλλ₯Ό νΈμΆνλ κ°μ²΄λ₯Ό λΆλ¦¬νμ¬ μμ€ν κ³Ό ν΄λμ€λ₯Ό λμ¨νκ² μ°κ²°ν μ μλ€.
ν΄λ¦½ 보λ APIκ° μ»€λ§¨λ ν¨ν΄κ³Ό μ μ¬ν©λλ€. Redux μ¬μ©μμΈ κ²½μ°, μ΄λ―Έ 컀맨λ ν¨ν΄μ μ¬μ©ν΄λ³Έ κ²μ΄λ€. λ©μ§ μκ° μ¬ν λλ²κΉ κΈ°λ₯μ νμ©νλ μμ μ, μμ μ λ€μ μ€ννκ±°λ μ€ν μ·¨μνλλ‘ μΆμ ν μμλ μΊ‘μν λ μμ μΌ λΏμ΄λ€. λ°λΌμ μκ° μ¬νμ΄ κ°λ₯ν΄μ‘λ€.
μ΄ μμ μλ μ¬λ¬ λ©μλκ°μλ SpecialMath
ν΄λμ€μ ν΄λΉ μ£Όμ μμ μ€νλ λͺ
λ Ή, μ¦ SpecialMath
ν΄λμ€μ κ°μ²΄λ₯Ό μΊ‘μννλ Command
ν΄λμ€κ° μλ€. Command
ν΄λμ€λ μ€ν λ λͺ¨λ λͺ
λ Ήμ μΆμ νμ¬ μ€ν μ·¨μ λ° μ¬μ€νν μ ν μμ
μ ν¬ν¨νλλ‘ κΈ°λ₯μ νμ₯νλ λ° μ¬μ©ν μ μλ€.
class SpecialMath {
constructor(num) {
this._num = num;
}
square() {
return this._num ** 2;
}
cube() {
return this._num ** 3;
}
squareRoot() {
return Math.sqrt(this._num);
}
}
class Command {
constructor(subject) {
this._subject = subject;
this.commandsExecuted = [];
}
execute(command) {
this.commandsExecuted.push(command);
return this._subject[command]();
}
}
// usage
const x = new Command(new SpecialMath(5));
x.execute('square');
x.execute('cube');
console.log(x.commandsExecuted); // ['square', 'cube']
μ΄ν°λ μ΄ν° ν¨ν΄ (Iterator Pattern)
μ΄ν°λ μ΄ν° ν¨ν΄μ κΈ°λ³Έ ννμ λ ΈμΆμν€μ§ μκ³ μ§κ³ κ°μ²΄ μμμ μμ°¨μ μΌλ‘ μ‘μΈμ€νλ λ°©λ²μ μ 곡νλ νλ μ€κ³ ν¨ν΄μ΄λ€.
μ΄ν°λ μ΄ν°λ€μ μ°λ¦¬κ° λμ λλ¬ν λκΉμ§ next()
λ₯Ό νΈμΆνμ¬ ν λ²μ νλμ© μμκ° μ ν΄μ§ μΌλ ¨μ κ°μΉλ€μ λ°μ λκ°λ νΉλ³ν μ’
λ₯μ νλμ νλ€. ES6μμ Iteratorμ Generatorμ λμ
μΌλ‘ μΈν΄ μ΄ν°λ μ΄ν° ν¨ν΄μ ꡬνμ΄ λ§€μ° κ°λ¨ν΄μ‘λ€.
μλμ λ κ°μ§ μκ° μμ΅λλ€. 첫째, νλ IteratorClass
λ Iterator μ€νμ μ¬μ©νκ³ , λ€λ₯Έ νλ iteratorUsingGenerator
λ Generator ν¨μλ₯Ό μ¬μ©νλ€.
Symbol.iterator
( Symbol
μ κΈ°λ³Έ λ°μ΄ν° νμ
μ μλ‘μ΄ μ’
λ₯μ) κ°μ²΄μ λν κΈ°λ³Έ λ°λ³΅μλ₯Ό μ§μ νλ λ° μ¬μ©λλ€. 컬λ μ
μ΄ for...of
루ν ꡬμ±μ μ¬μ©ν μ μλλ‘ Symbol.iterator
λ μ μν΄μΌνλ€. 첫 λ²μ§Έ μμμλ μμ±μλ₯Ό μ μνμ¬ μΌλΆ λ°μ΄ν° λͺ¨μμ μ μ₯ ν λ€μ λ°λ³΅μ μν next
λ©μλκ° ν¬ν¨ λ κ°μ²΄λ₯Ό λ°ννλ Symbol.iterator
λ₯Ό μ μνλ€.
λ λ²μ§Έ κ²½μ°, λ λ²μ§Έ κ²½μ°μλ Generator ν¨μλ₯Ό μ μνμ¬ λ°μ΄ν° λ°°μ΄μ μ λ¬νκ³ next
λ° yield
λ₯Ό μ¬μ©νμ¬ ν΄λΉ μμλ₯Ό λ°λ³΅μ μΌλ‘ λ°ννλ€. μ λλ μ΄ν° ν¨μλ μ΄ν°λ μ΄ν°μ ν©ν λ¦¬λ‘ μλνλ νΉμν μ νμ ν¨μμ΄λ©°, μ체 λ΄λΆ μνλ₯Ό λͺ
μ μ μΌλ‘ μ μ§νκ³ λ°λ³΅μ μΌλ‘ κ°μ μ°μΆ ν μ μλ€. μ체 μ€νμ£ΌκΈ°λ₯Ό μΌμμ€μ§νλ€κ° λ€μ μμν μ μλ€.
// using Iterator
class IteratorClass {
constructor(data) {
this.index = 0;
this.data = data;
}
[Symbol.iterator]() {
return {
next: () => {
if (this.index < this.data.length) {
return { value: this.data[this.index++], done: false };
} else {
this.index = 0; // to reset iteration status
return { done: true };
}
},
};
}
}
// using Generator
function* iteratorUsingGenerator(collection) {
var nextIndex = 0;
while (nextIndex < collection.length) {
yield collection[nextIndex++];
}
}
// usage
const gen = iteratorUsingGenerator(['Hi', 'Hello', 'Bye']);
console.log(gen.next().value); // 'Hi'
console.log(gen.next().value); // 'Hello'
console.log(gen.next().value); // 'Bye'
μ€μ¬μ ν¨ν΄ (Mediator Pattern)
μ€μ¬μ ν¨ν΄μ μΌλ ¨μ κ°μ²΄κ° μλ‘ μνΈ μμ©νλ λ°©μμ μΊ‘μννλ νλ λμμΈ ν¨ν΄μ λλ€. λμ¨ν κ²°ν©μ μ΄μ§νμ¬ κ°μ²΄κ° μλ‘ λͺ μμ μΌλ‘ μ°Έμ‘°λμ§ μλλ‘νμ¬ κ°μ²΄ κ·Έλ£Ήμ λν μ€μ κΆνμ μ 곡ν©λλ€.
μ΄ μμμλ Airplane
κ°μ²΄κ° μλ‘ μνΈ μμ©νλ λ°©μμ μ μ΄νλ ββμ€μ¬μλ‘ TrafficTower
κ° μμ΅λλ€. λͺ¨λ Airplane
κ°μ²΄λ TrafficTower
κ°μ²΄μ λ±λ‘λλ©°, Airplane
κ°μ²΄κ° λ€λ₯Έ λͺ¨λ Airplane
κ°μ²΄μ μ’ν λ°μ΄ν°λ₯Όλ°λ λ°©λ²μ μ²λ¦¬νλ μ€μ¬μ ν΄λμ€ κ°μ²΄μ΄λ€.
class TrafficTower {
constructor() {
this._airplanes = [];
}
register(airplane) {
this._airplanes.push(airplane);
airplane.register(this);
}
requestCoordinates(airplane) {
return this._airplanes.filter(plane => airplane !== plane).map(plane => plane.coordinates);
}
}
class Airplane {
constructor(coordinates) {
this.coordinates = coordinates;
this.trafficTower = null;
}
register(trafficTower) {
this.trafficTower = trafficTower;
}
requestCoordinates() {
if (this.trafficTower) return this.trafficTower.requestCoordinates(this);
return null;
}
}
// usage
const tower = new TrafficTower();
const airplanes = [new Airplane(10), new Airplane(20), new Airplane(30)];
airplanes.forEach(airplane => {
tower.register(airplane);
});
console.log(airplanes.map(airplane => airplane.requestCoordinates()))
// [20, 30], [10, 30], [10, 20](/Lee-hyuna/33-js-concepts-kr/wiki/20,-30],-[10,-30],-[10,-20)
κ΄μ°°μ ν¨ν΄ (Observer Pattern)
νλμ κ°μ²΄(publisher)κ° μνλ₯Ό λ³κ²½ν λ λ€λ₯Έ λͺ¨λ μ’ μ κ°μ²΄(subscribers)μκ² μλμΌλ‘ μλ¦¬κ³ μ λ°μ΄νΈ λλλ‘ κ°μ²΄ κ°μ μΌλλ€ μ’ μμ±μ μ μνλ μ€μν λμ λμμΈ ν¨ν΄μ΄λ€. μ΄κ²μ PubSub (publisher / subscribers) λλ μ΄λ²€νΈ λμ€ν¨μ² / 리μ€λ ν¨ν΄μ΄λΌκ³ λ νλ€. publishersλ λλλ‘ μ£Όμ λΌκ³ νλ©° subscribersλ λλλ‘ κ΄μ°°μλΌκ³ ν©λλ€.
addEventListener
λλ jQueryλ₯Ό μ¬μ©νλ€λ©΄ μ΄λ―Έμ΄ ν¨ν΄μ μ΄λ μ λ μ΅μ ν κ²μ΄λ€.
μ΄ μμμλ subscriber 컬λ μ
μμ Observer
ν΄λμ€μ κ°μ²΄λ₯Ό μΆκ°νκ³ μ κ±°νλ λ©μλκ° μλ κ°λ¨ν Subject
ν΄λμ€λ₯Ό λ§λ λ€. λν Subject
ν΄λμ€ κ°μ²΄μ λ³κ²½ μ¬νμ ꡬλ
νObserver
μ μ ννλ fire
λ©μλκ° μλ€. λ°λ©΄, Observer
ν΄λμ€λ λ΄λΆ μνμ ꡬλ
ν Subject
μμ μ νλ λ³κ²½μ λ°λΌ λ΄λΆ μνλ₯Ό μ
λ°μ΄νΈνλ λ©μλλ₯Ό κ°λλ€.
class Subject {
constructor() {
this._observers = [];
}
subscribe(observer) {
this._observers.push(observer);
}
unsubscribe(observer) {
this._observers = this._observers.filter(obs => observer !== obs);
}
fire(change) {
this._observers.forEach(observer => {
observer.update(change);
});
}
}
class Observer {
constructor(state) {
this.state = state;
this.initialState = state;
}
update(change) {
let state = this.state;
switch (change) {
case 'INC':
this.state = ++state;
break;
case 'DEC':
this.state = --state;
break;
default:
this.state = this.initialState;
}
}
}
// usage
const sub = new Subject();
const obs1 = new Observer(1);
const obs2 = new Observer(19);
sub.subscribe(obs1);
sub.subscribe(obs2);
sub.fire('INC');
console.log(obs1.state); // 2
console.log(obs2.state); // 20
μν ν¨ν΄ (State Pattern)
λ΄λΆ μν λ³κ²½μ λ°λΌ κ°μ²΄μ λμμ λ³κ²½ν μ μλ νλ λμμΈ ν¨ν΄μλ€. μν ν¨ν΄ ν΄λμ€κ° λ°ν ν κ°μ²΄λ ν΄λμ€λ₯Ό λ³κ²½νλ κ²μΌλ‘ μΈλ€. κ° κ°μ²΄ μ νμ΄ νΉμ μνλ₯Ό λνλ΄λ μ νλ κ°μ²΄ μΈνΈμ μν λ³ λ‘μ§μ μ 곡νλ€.
μ΄ ν¨ν΄μ μ΄ν΄νκΈ° μν΄ μ νΈλ±μ κ°λ¨ν μλ₯Ό λ€μ΄λ³΄μ. TrafficLight
ν΄λμ€λ λ΄λΆ μν (Red
, Yellow
λλ Green
ν΄λμ€μ κ°μ²΄)λ₯Ό κΈ°μ€μΌλ‘ λ°ννλ κ°μ²΄λ₯Ό λ³κ²½νλ€.
class TrafficLight {
constructor() {
this.states = [new GreenLight(), new RedLight(), new YellowLight()];
this.current = this.states[0];
}
change() {
const totalStates = this.states.length;
let currentIndex = this.states.findIndex(light => light === this.current);
if (currentIndex + 1 < totalStates) this.current = this.states[currentIndex + 1];
else this.current = this.states[0];
}
sign() {
return this.current.sign();
}
}
class Light {
constructor(light) {
this.light = light;
}
}
class RedLight extends Light {
constructor() {
super('red');
}
sign() {
return 'STOP';
}
}
class YellowLight extends Light {
constructor() {
super('yellow');
}
sign() {
return 'STEADY';
}
}
class GreenLight extends Light {
constructor() {
super('green');
}
sign() {
return 'GO';
}
}
// usage
const trafficLight = new TrafficLight();
console.log(trafficLight.sign()); // 'GO'
trafficLight.change();
console.log(trafficLight.sign()); // 'STOP'
trafficLight.change();
console.log(trafficLight.sign()); // 'STEADY'
trafficLight.change();
console.log(trafficLight.sign()); // 'GO'
trafficLight.change();
console.log(trafficLight.sign()); // 'STOP'
μ λ΅ ν¨ν΄ (Strategy Pattern)
νΉμ μμ μ λν λ체 μκ³ λ¦¬μ¦μ μΊ‘μν ν μ μλ νλ μ€κ³ ν¨ν΄μ΄λ€. λν μκ³ λ¦¬μ¦ μ νκ΅°μ μ μνκ³ ν΄λΌμ΄μΈνΈ κ°μμ΄λ μ§μμμ΄ λ°νμμ μνΈ κ΅ν ν μμλ λ°©μμΌλ‘ μκ³ λ¦¬μ¦μ μΊ‘μννλ€.
μλ μμμλ μΆκ·Όμ μν λͺ¨λ μ λ΅μ μΊ‘μννκΈ° μν΄ Commute
ν΄λμ€λ₯Ό λ§λ λ€. κ·Έλ° λ€μ Bus
, PersonalCar
λ° Taxi
μ μΈ κ°μ§ μ λ΅μ μ μνλ€. μ΄ ν¨ν΄μ μ¬μ©νμ¬ λ°νμμ Commute
κ°μ²΄μ trave
λ©μλμ μ¬μ©νλλ‘ κ΅¬νμ λ°κΏ μ μλ€.
// encapsulation
class Commute {
travel(transport) {
return transport.travelTime();
}
}
class Vehicle {
travelTime() {
return this._timeTaken;
}
}
// strategy 1
class Bus extends Vehicle {
constructor() {
super();
this._timeTaken = 10;
}
}
// strategy 2
class Taxi extends Vehicle {
constructor() {
super();
this._timeTaken = 5;
}
}
// strategy 3
class PersonalCar extends Vehicle {
constructor() {
super();
this._timeTaken = 3;
}
}
// usage
const commute = new Commute();
console.log(commute.travel(new Taxi())); // 5
console.log(commute.travel(new Bus())); // 10
ν νλ¦Ώ ν¨ν΄ (Template Pattern)
μ΄λ μκ³ λ¦¬μ¦μ 골격 λλ μμ ꡬνμ μ μνμ§λ§ μΌλΆ λ¨κ³λ₯Ό μλΈ ν΄λμ€λ‘ μ°κΈ°νλ λμ κΈ°λ° λμμΈ ν¨ν΄μ΄λ€. μλΈ ν΄λμ€λ μκ³ λ¦¬μ¦μ μΈλΆ ꡬ쑰λ₯Ό λ³κ²½νμ§ μκ³ μκ³ λ¦¬μ¦μ νΉμ λ¨κ³λ₯Ό μ¬μ μ ν μ μλ€.
μ΄ μμ μλ work
λ©μλλ₯Ό λΆλΆμ μΌλ‘ ꡬννλ Template ν΄λμ€ Employee
κ° μλ€. μλΈ ν΄λμ€κ° μ 체μ μΌλ‘ μλνλλ‘ λ©μλλ₯Ό ꡬνν΄μΌνλ€. κ·Έλ° λ€μ ν
νλ¦Ώ ν΄λμ€λ₯Ό νμ₯νκ³ κ΅¬ν 격차λ₯Ό λ©μ°κΈ° μν΄ νμν λ©μλλ₯Ό ꡬννλ λ κ°μ μλΈ ν΄λμ€ μΈ Developer
λ° Tester
λ₯Ό λ§λ λ€.
class Employee {
constructor(name, salary) {
this._name = name;
this._salary = salary;
}
work() {
return `${this._name} handles ${this.responsibilities() /* gap to be filled by subclass */}`;
}
getPaid() {
return `${this._name} got paid ${this._salary}`;
}
}
class Developer extends Employee {
constructor(name, salary) {
super(name, salary);
}
// details handled by subclass
responsibilities() {
return 'application development';
}
}
class Tester extends Employee {
constructor(name, salary) {
super(name, salary);
}
// details handled by subclass
responsibilities() {
return 'testing';
}
}
// usage
const dev = new Developer('Nathan', 100000);
console.log(dev.getPaid()); // 'Nathan got paid 100000'
console.log(dev.work()); // 'Nathan handles application development'
const tester = new Tester('Brian', 90000);
console.log(tester.getPaid()); // 'Brian got paid 90000'
console.log(tester.work()); // 'Brian handles testing'
κ²°λ‘
λμμΈ ν¨ν΄μ μννΈμ¨μ΄ μμ§λμ΄λ§μ μ€μνλ©° μΌλ°μ μΈ λ¬Έμ λ₯Ό ν΄κ²°νλ λ° λ§€μ° λμμ΄ λ μ μλ€. κ·Έλ¬λ μ΄κ²μ λ§€μ° κ΄λ²μν μ£Όμ μ΄λ©°, λμμΈ ν¨ν΄μ κ΄ν λͺ¨λ κ²μ μ΄ κΈμ ν¬ν¨μν€λ κ²μ λΆκ°λ₯νλ€. λ°λΌμ λͺ¨λ JavaScriptλ₯Ό μμ±νλ λ° μ€μ λ‘ λμμ΄ λ μμλ νλͺ©μ λν΄ μ§§κ³ κ°κ²°νκ² μ΄μΌκΈ°νλλ‘ κ³¨λλ€. λ μμΈν μμ λ³΄λ €λ©΄ μλμ μ± λ€μ μ΄ν΄λ³΄κΈΈ λ°λλ€.
- λμμΈ ν¨ν΄ : μ¬μ¬μ© κ°λ₯ν κ°μ²΄ μ§ν₯ μννΈμ¨μ΄μ μμ by Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides (Gang of Four)
- μλ° μ€ν¬λ¦½νΈ λμμΈ ν¨ν΄ μμ보기 by Addy Osmani
- μλ° μ€ν¬λ¦½νΈ ν¨ν΄ by Stoyan Stefanov