├── .gitignore
├── 02_코드_구성
└── README.md
├── 03_생성_패턴
└── README.md
├── 04_구조_패턴
├── ARUSANTIMO.md
├── DOHYUNG.md
├── HWANGTAN.md
├── JINHO.md
├── README.md
└── jaenam.md
├── 05_행동_패턴
├── BHKIM.md
├── DOHYUNG.md
├── HWANGWONJUN.md
├── JINHO.md
├── Jieun.md
├── READMD.md
├── jaenam.md
├── jina.md
└── junghyun.md
├── 06_함수형_프로그래밍
├── BHKIM.md
└── READMD.md
├── 07_모델_뷰_패턴
├── ARUSANTIMO.MD
├── BHKIM.md
├── DOHYUNG.md
├── JieunPark.md
├── READMD.md
├── bhkim_
│ ├── img1.png
│ └── img2.png
├── jaenam.md
├── jieunpark_
│ ├── 1.png
│ ├── 2.png
│ ├── 3.gif
│ └── 4.gif
├── jinho.md
├── jinho_
│ ├── img
│ │ └── MVC-MVP-MVVM.png
│ └── mvc
│ │ ├── EventDispatcher.js
│ │ ├── TaskController.js
│ │ ├── TaskModel.js
│ │ ├── TaskView.js
│ │ └── index.html
├── wonjun.md
└── wonjun_
│ ├── app.js
│ ├── bin
│ └── www
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ ├── javascripts
│ │ └── mvp.js
│ └── stylesheets
│ │ ├── style.css
│ │ └── style.less
│ ├── routes
│ └── index.js
│ └── views
│ ├── error.hbs
│ ├── index.hbs
│ ├── layout.hbs
│ └── mvc.hbs
├── 08_웹_패턴
└── READMD.md
├── 09_메시징_패턴
├── BHKIM.md
├── DOHYUNG.md
├── READMD.md
├── bhkim_
│ ├── FanOutInWebWorker.js
│ └── fanoutin.html
└── jaenam.md
├── 10_테스트를_위한_패턴
└── READMD.md
├── 11_고급_패턴
├── READMD.md
└── jaenam.md
├── 12_오늘날의_ES6_솔루션
└── READMD.md
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
--------------------------------------------------------------------------------
/02_코드_구성/README.md:
--------------------------------------------------------------------------------
1 | # 2. 코드 구성
2 |
3 | 코드 모듈화의 필요성과 자바스크립트에서 모듈을 생성 하는 방법을 알아본다.
4 |
5 | - 전역 범위
6 | - 객체
7 | - 프로토타입
8 | - 프로토타입 상속
9 | - 모듈
10 | - ES6 Class 에서 모듈
11 |
12 | 구조적 프로그래밍은 뵘-야코피니 이론에 기반한다.
13 | - 서브 프로그램의 **순차** 실행
14 | - 두개의 서브 프로그램의 **조건부** 실행
15 | - 조건이 참이 될 때까지 하나의 서브 프로그램을 **반복** 실행
16 |
17 | 모듈은 정적인 메소드만 포함하는 클래스와 같다.
18 |
19 | ## 전역 범위 (Global Scope)
20 | ```js
21 | var hello = "hello"
22 | world = "world"
23 |
24 | console.log (window.hello + " " + window.world) // "hello world"
25 | ```
26 | 전역 변수는 선언 함으로서 최상위 컨테이너인 window 에 할당 되고
27 |
28 | Node.js 에서는 global 객체에 할당 된다.
29 |
30 | 전역 변수는 다른 코드에 의해 쉽게 영향을 받을 수 있기 때문에 지양 하는 것이 좋다.
31 |
32 | ## 객체 (Object)
33 | - Undefined
34 | - Null
35 | - Boolean
36 | - String
37 | - Number
38 | - Symbol (ES6 New)
39 |
40 | Null, Undefined 를 제외한 원시 데이터 타입은 타입 래퍼를 가진다.
41 |
42 | 이는 객체와 원시 데이터를 혼합 해서 사용하는 자바와 동일한 모델 이다.
43 |
44 | 필요한경우 박싱과 언박싱을 지원한다.
45 |
46 | ```js
47 | /* 박싱과 언박싱 */
48 | var numberOne = new Number(1)
49 | var numberTwo = 2
50 | typeof numberOne // 객체
51 | typeof numberTwo // 숫자
52 | var numberThree = numberOne + numberTwo
53 | typeof numberThree // 숫자
54 |
55 | /* 객체 생성 */
56 | var objectOne = {}
57 | typeof objectOne // 객체
58 | var objectTwo = new Object()
59 | typeof objectTwo // 객체
60 |
61 | /* 객체 속성 추가 */
62 | var objectOne = { value: 7 }
63 | var objectTwo = { }
64 | objectTwo.value = 7
65 |
66 | /* 객체에 함수 추가 & 구문 혼합 */
67 | var functionObject = {
68 | greeting: "hello world",
69 | doThings: function() {
70 | console.log(this.greeting)
71 | this.doOtherThings()
72 | },
73 | doOtherThings: function() {
74 | console.log(this.greeting.split("").reverse().join(""))
75 | }
76 | }
77 | functionObject.doThings() // "hello world"
78 | ```
79 |
80 | this 키워드는 함수의 소유자에 바인딩되어 있지만, 종종 우리가 기대하는 바와 다를수 있다.
81 |
82 | ## 프로토 타입 구축
83 | 구조체를 사용해 객체를 생성할 경우 복수의 객체를 생성하는데 **시간**이 많이 걸리고 많은 **메모리**를 필요로 한다.
84 |
85 | 생성 방식이 동일하더라도 각각의 객체는 완전히 다른 객체이다.
86 |
87 | 이는 함수를 정의하는 데 사용된 메모리가 인스턴스 간에 공유되지 않음을 의미한다.
88 |
89 | 흥미로운 점은 인스턴스를 변경하지 않고도 각각 독립적인 클래스의 인스턴스를 다시 정의할수 있다.
90 |
91 | 이와 같은 방식으로 단일 인스턴스의 함수나 이미 정의된 객체를 변경하는 방법을 몽키 패치라 한다.
92 |
93 | **몽키 패치는** **라이브러리 코드를 처리할때 유용**할수 있지만, **대체로 혼란**을 초례 할수 있다.
94 | ```js
95 | var Castle = function(name) {
96 | this.name = name
97 | this.build = function() {
98 | console.log(this.name)
99 | }
100 | }
101 |
102 | var instance1 = new Castle("Winterfell")
103 | var instance2 = new Castle("Harrenhall")
104 |
105 | instance1.build = function() { console.log("Moat Cailin"); } // monkey patching
106 | instance1.build() // "Moat Cailin"
107 | instance2.build() // "Harrenhall"
108 | ```
109 |
110 | 프로토타입에 함수를 할당하는 **장점**은 단 **하나의 함수 사본만이 생성**된다는 점이다.
111 |
112 | 나중에 객체의 프로토타입을 변경한 경우 프로토타입을 공유하는 모든 객체는 새로운 기능으로 업데이트 된다.
113 |
114 | ```js
115 | var Castle = function(name) {
116 | this.name = name
117 | }
118 |
119 | Castle.prototype.build = function() { console.log(this.name); }
120 |
121 | var instance1 = new Castle("Winterfell")
122 |
123 | Castle.prototype.build = function() { console.log(this.name.replce("Winterfell", "Moat Cailln"))}
124 |
125 | instance1.build() // "Moat Cailln"
126 | ```
127 |
128 | 객체를 상속할때 프로토타입의 장점을 이용하는것이 좋다.
129 |
130 | ES5 에 추가된 Object.create 함수를 사용하는것인데 필드를 설명하는 옵션 객체를 전달 할수있고 다음과 같은 선택 필드로 구성된다.
131 |
132 | - writable: 쓰기 가능 여부
133 | - configurable: 파일이 객체로 부터 제거 가능한지, 생성후 추가적 구성을 지원하는지 여부
134 | - enumerable: 객체의 속성을 열거하는 동안 속성이 나열될수 있는지 확인
135 | - value: 필드의 기본값
136 |
137 | ```js
138 | var instance3 = Object.create(Castle.prototype, {
139 | name: {
140 | value: "Winterfell",
141 | writable: false,
142 | }
143 | })
144 |
145 | instance3.bulid() // "Winterfell"
146 | instance3.name = "Highgarden"
147 | instance3.build() // "Winterfell"
148 | ```
149 | ## 상속
150 | #### 고통스러운 방법
151 | ```js
152 | var Castle = function() {}
153 | Castle.prototype.build = function() { console.log("Castle built"); }
154 |
155 | var Winterfell = function() {}
156 | Winterfell.prototype.build = Castle.prototype.build
157 | Winterfell.prototype.addGodsWood = function() {}
158 |
159 | var winterfell = new Winterfell()
160 | winterfell.build() // "Castle built"
161 | ```
162 |
163 | #### 조금덜 고통스러운 추상화
164 | ```js
165 | function clone(source, destination) {
166 | for (var attr in source.prototype) {
167 | destination.prototype[attr] = source.prototype[attr]
168 | }
169 | }
170 |
171 | var Castle = function() {}
172 | Castle.prototype.build = function() { console.log("Castle built"); }
173 |
174 | var Winterfell = function() {}
175 | clone(Castle, Winterfell);
176 |
177 | var winterfell = new Winterfell()
178 | winterfell.build() // "Castle built"
179 | ```
180 |
181 | ## 모듈
182 | ```js
183 | var Westeros = Westeros || {}
184 | Westeros.Castle = function(name) { this.name = name; }
185 | Westeros.Castle.prototype.Build = function() { console.log("Castle built: " + this.name); }
186 |
187 | // 단일 계층 구조
188 | var Westeros = Westeros || {}
189 | Westeros.Structures.Castle = function(name) { this.name = name; }
190 | Westeros.Structures.Castle.prototype.Build = function() { console.log("Castle built: " + this.name); }
191 | var winterfell = new Westeros.Structures.Castle("Winmterfell")
192 | winterfell.Build()
193 |
194 | //계층적으로 변환
195 | var Castle = (function() {
196 | function Castle(name) {
197 | this.name = name
198 | }
199 | Castle.prototype.build = function() { console.log("Castle built: " + this.name); }
200 | return Castle
201 | })()
202 |
203 | Westeros.Structures.Castle = Caslte
204 |
205 | //비교적 쉬운 상속
206 | var __extends = this.__extends || function (d, b) {
207 | for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]
208 | function __() { this.constructor = d; }
209 | __.prototype = b.prototype
210 | d.prototype = new __()
211 | }
212 |
213 | var BaseStructure = (function() {
214 | function BaseStructure() { }
215 | return BaseStructure
216 | })()
217 |
218 | Structures.BaseStructure = BaseStructure
219 |
220 | var Castle = (function(_super) {
221 | __extends(Castle, _super)
222 |
223 | function Castle(name) {
224 | this.name = name
225 | _super.call(this)
226 | }
227 | Castle.prototype.Build = function() { console.log("Castle built: " + this.name); }
228 | return Castle
229 | })(BaseStructure)
230 |
231 | // 클로저 구문으로 구현한 전체 모듈
232 | var Westeros;
233 | (function(Westeros) {
234 | (function(Structures) {
235 | var Castle = (function() {
236 | function Castle(name) {
237 | this.name = name
238 | }
239 | Castle.prototype.Build = function() { console.log("Castle built: " + this.name); }
240 | return Castle
241 | })()
242 | Structures.Castle = Castle
243 | // 추가된 클래스
244 | var Wall = (function() {
245 | function Wall() {
246 | console.log("Wall constructed")
247 | }
248 | return Wall
249 | })()
250 | Structures.Wall = Wall
251 | })(Westeros.Structures || (Westeros.Structures = {}))
252 | var Structures = Westeros.Structures
253 | })(Westeros || (Westeros = {}))
254 | ```
255 |
256 |
257 | ## ES6 Class 와 모듈
258 | ```js
259 | class Castle extends Westeros.Structures.BaseStructure {
260 | constructor(name, allegience) {
261 | super(name)
262 | }
263 |
264 | Build() {
265 | super.Build()
266 | }
267 | }
268 | ```
269 |
270 | ```js
271 | // ts module -> namespace
272 | namespace Westros {
273 | export function Rule(rulerName, house) {
274 | return "Long live " + rulerName + " of house " + house;
275 | }
276 | }
277 |
278 | Westros.Rule("Rob Stark", "Stark");
279 | ```
280 |
281 | https://basarat.gitbooks.io/typescript/content/docs/project/namespaces.html
282 | https://stackoverflow.com/questions/38582352/module-vs-namespace-import-vs-require-typescript
283 |
284 |
285 |
--------------------------------------------------------------------------------
/03_생성_패턴/README.md:
--------------------------------------------------------------------------------
1 | # 3. 생성 패턴
2 |
3 | > Westeros 라는 전역 네임스페이스는 생략하였다.
4 |
5 | ```js
6 | // 결합상황 p72
7 | class Ruler {
8 | constructor () {
9 | this.house = new Houses.Targaryen()
10 | }
11 | }
12 | ```
13 |
14 | ## 3-1. 추상 팩토리
15 |
16 | ```js
17 | // p78
18 | class KingJoffery {
19 | makeDecision () {}
20 | marry () {}
21 | }
22 |
23 | class LordTywin {
24 | makeDecision () {}
25 | }
26 |
27 | class LannisterFactory {
28 | getKing () {
29 | return new KingJoffery()
30 | }
31 | getHandOfTheKing () {
32 | return new LordTywin()
33 | }
34 | }
35 |
36 | class TargaryenFactory {
37 | getKing () {
38 | return new KingAerys()
39 | }
40 | getHandOfTheKing () {
41 | return new LordConnington()
42 | }
43 | }
44 | ```
45 |
46 | -> 지배가문을 필요로 하는 클래스
47 | ```js
48 | // p79
49 | class CourtSession {
50 | constructor (abstractFactory) {
51 | this.abstractFactory = abstractFactory
52 | this.COMPLAINT_THRESHOLD = 10
53 | }
54 | complaintPresented (complaint) {
55 | if (complaint.severity < this.COMPLAINT_THRESHOLD) {
56 | this.abstractFactory.getHandOfTheKing().makeDecision()
57 | } else {
58 | this.abstractFactory.getKing().makeDecision()
59 | }
60 | }
61 | }
62 |
63 | const targaryenCourtSession = new CourtSession(new TargaryenFactory())
64 | targaryenCourtSession.complaintPresented({ severity: 8 })
65 | targaryenCourtSession.complaintPresented({ severity: 12 })
66 |
67 | const lannisterCourtSession = new CourtSession(new LannisterFactory())
68 | lannisterCourtSession.complaintPresented({ severity: 8 })
69 | lannisterCourtSession.complaintPresented({ severity: 12 })
70 | ```
71 |
72 | ## 3-2. 빌더
73 |
74 | ```js
75 | // p81
76 | class Event {
77 | constructor (name) {
78 | this.name = name
79 | }
80 | }
81 | class Prize {
82 | constructor (name) {
83 | this.name = name
84 | }
85 | }
86 | class Attendee {
87 | constructor (name) {
88 | this.name = name
89 | }
90 | }
91 | class Tournament {
92 | constructor () {
93 | this.events = []
94 | this.attendees = []
95 | this.prizes = []
96 | }
97 | }
98 | class LannisterTournamentBuilder {
99 | build () {
100 | const tournament = new Tournament()
101 | tournament.events.push(new Event('Jourst'), new Event('Melee'))
102 | tournament.attendees.push(new Attendee('Jamie'))
103 | tournament.prizes.push(new Prize('Gold'), new Prize('More Gold'))
104 | return tournament
105 | }
106 | }
107 | class BaratheonTournamentBuilder {
108 | build () {
109 | const tournament = new Tournament()
110 | tournament.events.push(new Event('Jourst'), new Event('Melee'))
111 | tournament.attendees.push(new Attendee('Stannis'), new Attendee('Robert'))
112 | return tournament
113 | }
114 | }
115 | class TournamentBuilder {
116 | build (builder) {
117 | return builder.build()
118 | }
119 | }
120 | ```
121 |
122 | >> Builder Pattern example
123 | ```js
124 | class Pizza {
125 | setDough (dough) {
126 | this.dough = dough
127 | }
128 | setSauce (sauce) {
129 | this.sauce = sauce
130 | }
131 | setTopping (topping) {
132 | this.topping = topping
133 | }
134 | }
135 |
136 | class PizzaBuilder {
137 | createPizza () {
138 | this.pizza = new Pizza()
139 | }
140 | getPizza () {
141 | return this.pizza
142 | }
143 | buildDough () {
144 | if(new.target === PizzaBuilder) {
145 | throw new Error('should overide this method!')
146 | }
147 | }
148 | buildSauce () {
149 | if(new.target === PizzaBuilder) {
150 | throw new Error('should overide this method!')
151 | }
152 | }
153 | buildTopping () {
154 | if(new.target === PizzaBuilder) {
155 | throw new Error('should overide this method!')
156 | }
157 | }
158 | }
159 |
160 | class HawaiianPizzaBuilder extends PizzaBuilder {
161 | buildDough () {
162 | this.pizza.setDough('cross')
163 | }
164 | buildSauce () {
165 | this.pizza.setSauce('mild')
166 | }
167 | buildTopping () {
168 | this.pizza.setTopping('ham & pineapple')
169 | }
170 | }
171 | class SpicyPizza extends PizzaBuilder {
172 | buildDough () {
173 | this.pizza.setDough('pan baked')
174 | }
175 | buildSauce () {
176 | this.pizza.setSauce('hot')
177 | }
178 | buildTopping () {
179 | this.pizza.setTopping('pepperoni & salami')
180 | }
181 | }
182 |
183 | class Cook {
184 | setPizzaBuilder (builder) {
185 | this.pizzaBuilder = builder
186 | }
187 | getPizza () {
188 | return this.pizzaBuilder.getPizza()
189 | }
190 | cookPizza () {
191 | this.pizzaBuilder.createPizza()
192 | this.pizzaBuilder.buildDough()
193 | this.pizzaBuilder.buildSauce()
194 | this.pizzaBuilder.buildTopping()
195 | }
196 | }
197 |
198 | const cook = new Cook()
199 | cook.setPizzaBuilder(new HawaiianPizzaBuilder())
200 | cook.cookPizza()
201 | console.log(cook.getPizza())
202 | ```
203 |
204 | ## 3-3. 팩토리 메소드
205 |
206 | ```js
207 | // p86
208 | class WateryGod {
209 | prayTo () {}
210 | }
211 | class AncientGods {
212 | paryTo () {}
213 | }
214 | class DefaultGod {
215 | prayTo () {}
216 | }
217 |
218 | class GodFactory {
219 | static Build (godName) {
220 | switch(godName) {
221 | case 'watery': return new WateryGod()
222 | case 'ancient': return new AncientGods()
223 | default: return new DefaultGod()
224 | }
225 | }
226 | }
227 |
228 | class GodDeterminant {
229 | constructor (religionName, prayerPurpose) {
230 | this.religionName = religionName
231 | this.prayerPurpose = prayerPurpose
232 | }
233 | }
234 |
235 | class Prayer {
236 | pray (godName) {
237 | GodFactory.Build(godName).prayTo()
238 | }
239 | }
240 | ```
241 |
242 | >> Factory Method Pattern examples
243 | ```js
244 | class Car {
245 | constructor (type, color) {
246 | this.type = type
247 | this.color = color
248 | }
249 | }
250 | class CarFactoryMethod {
251 | produce (carName, color) {
252 | return new Car(carName, color)
253 | }
254 | }
255 | const cf = new CarFactoryMethod()
256 | const bmw = cf.produce('Bmw', 'White')
257 | const ss = cf.produce('Audi', 'Black')
258 | ```
259 |
260 | ```js
261 | class Pizza {
262 | constructor () {
263 | this.price = 0
264 | }
265 | getPrice () {
266 | return this.price
267 | }
268 | }
269 | class HamAndMushroomPizza extends Pizza{
270 | constructor () {
271 | super()
272 | this.price = 8.50
273 | }
274 | }
275 | class DeluxePizza extends Pizza {
276 | constructor () {
277 | super()
278 | this.price = 10.50
279 | }
280 | }
281 | class DefaultPizza extends Pizza {
282 | constructor () {
283 | super()
284 | this.price = 6.50
285 | }
286 | }
287 | class PizzaFactoryMethod {
288 | createPizza (type) {
289 | switch(type) {
290 | case 'Ham and Mushroom': return new HamAndMushroomPizza()
291 | case 'Deluxe': return new DeluxePizza()
292 | default: return new DefaultPizza()
293 | }
294 | }
295 | }
296 | const pfm = new PizzaFactoryMethod()
297 | const hnm = pfm.createPizza('Ham and Mushroom')
298 | const dlx = pfm.createPizza('Deluxe')
299 | console.log(hnm.getPrice(), dlx.getPrice())
300 | ```
301 |
302 |
303 | ## 3-4. 단일체 (Singleton)
304 |
305 | ```js
306 | // p89
307 | const Wall = (() => {
308 | class Wall {
309 | constructor () {
310 | this.height = 0
311 | if(Wall._instance) {
312 | return Wall._instance
313 | }
314 | Wall._instance = this
315 | }
316 | setHeight (height) {
317 | this.height = height
318 | }
319 | getStatus () {
320 | console.log(`Wall is ${this.height} meters tall.`)
321 | }
322 | static getInstance () {
323 | if(!Wall._instance) {
324 | Wall._instance = new Wall()
325 | }
326 | return Wall._instance
327 | }
328 | }
329 | Wall._instance = null
330 | return Wall
331 | })()
332 | ```
333 |
334 | ## 3-5. 프로토타입
335 |
336 | ```js
337 | // p92
338 | class Lannister {
339 | clone () {
340 | return Object.assign(new Lannister(), this)
341 | }
342 | }
343 | const jamie = new Lannister()
344 | jamie.swordSkills = 9
345 | jamie.charm = 6
346 | jamie.wealth = 10
347 | const tyrion = jamie.clone()
348 | tyrion.charm = 10
349 | console.log(tyrion)
350 | ```
351 |
--------------------------------------------------------------------------------
/04_구조_패턴/ARUSANTIMO.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/04_구조_패턴/DOHYUNG.md:
--------------------------------------------------------------------------------
1 | # 4장 - Structural Pattern
2 |
3 | 구조적 패턴 소개
4 |
5 | Gist - [Mastering Javascript Design Pattern Chapter 04 · GitHub](https://gist.github.com/emaren84/39c53a112f6f6ab21c0dd56c107da384)
6 |
7 | ## Adapter
8 |
9 | - 주어진 인터페이스를 만족하는 추상화된 클래스를 만드는 것. 본래 객체를 숨긴 클래스를 생성한 뒤 숨겨진 객체의 메서드를 대신 호출해준다.
10 | - 예를 들어 서드파티 라이브러리등의 외부 객체를 사용할 때 그 객체의 인터페이스를 바로 사용하기에는 복잡하고 절차가 많을 수 있다
11 | - 이를 포장한 객체를 만들어서 더 단순화된 메서드를 제공한다 -> 메서드 하나가 라이브러리의 메서드 여러개를 같이 수행해주는 등
12 | - 라이브러리를 직접 작성할 때도 내부 메서드를 감추고 제한된 기능만 제공하는 용도로 사용할 수 있다.
13 | - 유의해야할 점은 **Adapter** 라는 이름을 래핑 클래스에 함부로 사용해서 클라이언트가 자신이 어댑터를 호출하고 있다는 사실을 노출하는 일을 피하는 것이다.
14 | - 어댑의 규모가 너무 커지면 ‘단일 책임 원칙’ 을 잘 따르는 클래스를 만들고 있는지 다시 한번 생각해봐야 한다.
15 |
16 | ```ts
17 | interface Ship {
18 | SetRudderAngleTo(angle: number);
19 | SetSailConfiguration(configuration: SailConfiguration);
20 | SetSailAngle(SailId: number, sailAngle: number);
21 | GetCurrentBearing(): number;
22 | GetCurrentSpeedEstimate(): number;
23 | ShiftCrewWeightTo(weightToShift: number, locationId: number);
24 | }
25 |
26 | interface SailConfiguration {
27 | crews: number;
28 | foods: number;
29 | }
30 |
31 | interface SimpleShip {
32 | TurnLeft();
33 | TurnRight();
34 | GoFoward();
35 | }
36 |
37 | // 예제 코드의 구현방법이 잘못되었다고 느끼는 부분이 있는데,
38 | // Apater에 사용할 Ship을 주입받지 않고 외부에 글로벌 변수에서 참고한다는 점이다.
39 | class ShipAdater implements SimpleShip {
40 | private ship: Ship;
41 | constructor(ship: Ship) {
42 | this.ship = ship;
43 | }
44 |
45 | TurnLeft() {
46 | this.ship.SetRudderAngleTo(-30);
47 | this.ship.SetSailAngle(3, 12);
48 | }
49 |
50 | TurnRight() {
51 | this.ship.SetRudderAngleTo(30);
52 | this.ship.SetSailAngle(5, -9);
53 | }
54 |
55 | GoFoward() {
56 | this.ship.SetRudderAngleTo(0);
57 | this.ship.SetSailAngle(0, 0);
58 | this.ship.GetCurrentSpeedEstimate();
59 | }
60 | }
61 |
62 | // 메서드의 구현부는 생략
63 | class Boat implements Ship {
64 | SetRudderAngleTo(angle: number) {
65 | throw new Error("Method not implemented.");
66 | }
67 | SetSailConfiguration(configuration: SailConfiguration) {
68 | throw new Error("Method not implemented.");
69 | }
70 | SetSailAngle(SailId: number, sailAngle: number) {
71 | throw new Error("Method not implemented.");
72 | }
73 | GetCurrentBearing(): number {
74 | throw new Error("Method not implemented.");
75 | }
76 | GetCurrentSpeedEstimate(): number {
77 | throw new Error("Method not implemented.");
78 | }
79 | ShiftCrewWeightTo(weightToShift: number, locationId: number) {
80 | throw new Error("Method not implemented.");
81 | }
82 | }
83 |
84 | const ship = new ShipAdater(new Boat);
85 | ship.GoFoward();
86 | ship.TurnLeft();
87 | ```
88 |
89 | ## Bridge
90 |
91 | - 서로 다른 두 객체가 같은 인터페이스를 사용할 수 있도록 추상화된 인터페이스를 만드는 패턴인가?
92 | - 실질적으로 자바스크립트에서는 `interface` 가 없기 때문에 Adapter, Bridge 가 거의 유사한 패턴으로 동작한다.
93 | - 이렇게 객체를 프록시화하는 패턴은 특히 팩토리 메서드 패턴과 잘 어울린다.
94 |
95 | ```ts
96 | namespace Religion {
97 | export interface God {
98 | prayTo(withSomething?: Sacrifice | HumanSacrifice | PrayerPurpose): void;
99 | }
100 |
101 | export class OldGods implements God {
102 | prayTo(sacrifice: Sacrifice) {
103 | console.log('We Old Gods hear your prayer');
104 | }
105 | }
106 |
107 | export class DrownedGod {
108 | prayTo(humanSacrifice: HumanSacrifice) {
109 | console.log('*BUBBLE* GURGLE');
110 | }
111 | }
112 |
113 | export class SevenGods {
114 | prayTo(prayerPurpose: PrayerPurpose) {
115 | console.log(
116 | 'Sorry there are a lot of us, it gets confusing here. Did you pray for something?'
117 | );
118 | }
119 | }
120 |
121 | class Sacrifice {}
122 | class HumanSacrifice {}
123 | class PrayerPurpose {}
124 |
125 | export class PrayerPurposeProvider {
126 | GetPurpose() {
127 | return new PrayerPurpose();
128 | }
129 | }
130 |
131 | export class OldGodsAdapter {
132 | constructor(private oldGoods: OldGods) {}
133 |
134 | prayTo() {
135 | const sacrifice = new Sacrifice();
136 | this.oldGoods.prayTo(sacrifice);
137 | }
138 | }
139 |
140 | export class DrownedGodAdapter {
141 | constructor(private drownedGod: DrownedGod) {}
142 |
143 | prayTo() {
144 | const sacrifice = new HumanSacrifice();
145 | this.drownedGod.prayTo(sacrifice);
146 | }
147 | }
148 |
149 | export class SevenGodsAdapter {
150 | constructor(
151 | private sevenGods: SevenGods,
152 | private prayerPurposeProvider: PrayerPurposeProvider
153 | ) {}
154 |
155 | prayTo() {
156 | this.sevenGods.prayTo(this.prayerPurposeProvider.GetPurpose());
157 | }
158 | }
159 | }
160 |
161 | const god1 = new Religion.SevenGodsAdapter(
162 | new Religion.SevenGods(),
163 | new Religion.PrayerPurposeProvider()
164 | );
165 | const god2 = new Religion.DrownedGodAdapter(new Religion.DrownedGod());
166 | const god3 = new Religion.OldGodsAdapter(new Religion.OldGods());
167 |
168 | const gods: Religion.God[] = [god1, god2, god3];
169 |
170 | for (const god of gods) {
171 | god.prayTo();
172 | }
173 | ```
174 |
175 | ## Composite
176 |
177 | - 상속을 사용하게 되면 아주 강한 결속이 발생하고 재사용이 어렵게 된다.
178 | - 이럴 때 Composite(조합) 을 사용하여 클래스 사이에 조합을 하는 방식을 사용한다.
179 | - 중요한 점은 각 컴포넌트의 자손을 인터페이스만 맞다면 교체 가능하다는 점이다.
180 |
181 | ```ts
182 | namespace Composite {
183 | interface IMenuComponent {
184 | render(parentElement: HTMLElement): void;
185 | }
186 |
187 | interface IMenuItem extends IMenuComponent {}
188 |
189 | interface IMenu extends IMenuComponent {
190 | children: IMenuComponent[];
191 | }
192 |
193 | class MenuItemLink implements IMenuItem {
194 | constructor(public displayName: string, public url: string) {}
195 |
196 | render(parentElement: HTMLElement) {
197 | const link: HTMLAnchorElement = document.createElement('a');
198 | link.textContent = this.displayName;
199 | link.href = this.url;
200 | parentElement.appendChild(link);
201 | }
202 | }
203 |
204 | class MenuItemImageLink implements IMenuItem {
205 | constructor(
206 | public displayName: string,
207 | public url: string,
208 | public imageUrl: string
209 | ) {}
210 |
211 | render(parentElement: HTMLElement) {
212 | const link: HTMLAnchorElement = document.createElement('a');
213 | link.href = this.url;
214 | const img: HTMLImageElement = document.createElement('img');
215 | img.src = this.imageUrl;
216 | link.appendChild(img);
217 |
218 | const text = document.createTextNode(this.displayName);
219 | link.appendChild(text);
220 |
221 | parentElement.appendChild(link);
222 | }
223 | }
224 |
225 | class Menu implements IMenu {
226 | public children: IMenuComponent[] = [];
227 |
228 | constructor(public displayName?: string) {}
229 |
230 | render(parentElement: HTMLElement) {
231 | if (this.displayName) {
232 | parentElement.appendChild(document.createTextNode(this.displayName));
233 | }
234 |
235 | const ul: HTMLUListElement = document.createElement('ul');
236 |
237 | this.children.forEach(child => {
238 | const li: HTMLLIElement = document.createElement('li');
239 | child.render(li);
240 | ul.appendChild(li);
241 | });
242 |
243 | parentElement.appendChild(ul);
244 | }
245 | }
246 |
247 | window.addEventListener('load', function() {
248 | const menu: IMenu = new Menu();
249 | for (var i = 1; i <= 3; i++) {
250 | menu.children.push(new MenuItemLink('Link ' + i, '?id=' + i));
251 | }
252 |
253 | menu.children.push(
254 | new MenuItemImageLink(
255 | 'Contact',
256 | 'mailto:info@sample.com',
257 | 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAITSURBVBgZpcHLThNhGIDh9/vn7/RApwc5VCmFWBPi1mvwAlx7BW69Afeu3bozcSE7E02ILjCRhRrds8AEbKVS2gIdSjvTmf+TYqLu+zyiqszDMCf75PnnnVwhuNcLpwsXk8Q4BYeSOsWpkqrinJI6JXVK6lSRdDq9PO+19vb37XK13Hj0YLMUTVVyWY//Cf8IVwQEGEeJN47S1YdPo4npDpNmnDh5udOh1YsZRcph39EaONpnjs65oxsqvZEyTaHdj3n2psPpKDLBcuOOGUWpZDOG+q0S7751ObuYUisJGQ98T/Ct4Fuo5IX+MGZr95jKjRKLlSxXxFxOEmaaN4us1Upsf+1yGk5ZKhp8C74H5ZwwCGO2drssLZZo1ouIcs2MJikz1oPmapHlaoFXH1oMwphyTghyQj+MefG+RblcoLlaJG/5y4zGCTMikEwTctaxXq/w9kuXdm9Cuzfh9acujXqFwE8xmuBb/hCwl1GKAnGccDwIadQCfD9DZ5Dj494QA2w2qtQW84wmMZ1eyFI1QBVQwV5GiaZOpdsPaSwH5HMZULi9UmB9pYAAouBQbMHHrgQcnQwZV/KgTu1o8PMgipONu2t5KeaNiEkxgAiICDMCCFeEK5aNauAOfoXx8KR9ZOOLk8P7j7er2WBhwWY9sdbDeIJnwBjBWBBAhGsCmiZxPD4/7Z98b/0QVWUehjkZ5vQb/Un5e/DIsVsAAAAASUVORK5CYII='
258 | )
259 | );
260 |
261 | const subMenu: IMenu = new Menu('Sub Menu');
262 | for (var i = 1; i <= 2; i++) {
263 | subMenu.children.push(new MenuItemLink('Sub Link ' + i, '?id=' + i));
264 | }
265 |
266 | menu.children.push(subMenu);
267 |
268 | const contentDiv = document.getElementById('output');
269 | menu.render(contentDiv);
270 | });
271 | }
272 | ```
273 |
274 | ## Decorator
275 |
276 | - 해당 컴포넌트의 하위 클래스를 만드는 방법을 대체할 수 있다. 보통 하위 클래스 생성은 컴파일 타임에 작동하고 강한 결합을 보이지만, 데코레이터는 런타임에서 작동하기 때문에 이런 문제가 없다.
277 | - Adapter, Bridge 패턴과 유사하게 보일 수 있으나 데코레이터는 타겟 객체의 메서드를 프록시만 해주는 것이 아니라 직접 변형을 가하면서 동작한다.
278 | - 기존의 코드를 이 패턴으로 많이 대체하고자 하는 유혹에 빠질 수 있으나 유지보수성을 잘 고려해야 한다.
279 | - 특히 상속이 제한된 환경에서 유용하게 사용될 수 있다.
280 |
281 | ```ts
282 | interface IArmor {
283 | calculateDamageFromHit(hit: IHit);
284 | getArmorIntegrity(): number;
285 | }
286 |
287 | interface IHit {
288 | location: string;
289 | weapon: string;
290 | strength: number;
291 | }
292 |
293 | class BasicArmor implements IArmor {
294 | constructor(private baseArmorPoint: number = 1) {}
295 |
296 | calculateDamageFromHit(hit: IHit) {
297 | console.log(`You got damage in ${hit.location} by ${hit.weapon}`);
298 | return this.baseArmorPoint * hit.strength;
299 | }
300 |
301 | getArmorIntegrity() {
302 | return this.baseArmorPoint;
303 | }
304 | }
305 |
306 | class ChainMail implements IArmor {
307 | constructor(private decoratedArmor: IArmor) {}
308 |
309 | calculateDamageFromHit(hit: IHit) {
310 | const reducedHit = {
311 | ...hit,
312 | strength: hit.strength * 0.8,
313 | };
314 |
315 | return this.decoratedArmor.calculateDamageFromHit(reducedHit);
316 | }
317 |
318 | getArmorIntegrity() {
319 | return 0.9 * this.decoratedArmor.getArmorIntegrity();
320 | }
321 | }
322 |
323 | const armor = new ChainMail(new BasicArmor());
324 | console.log(
325 | armor.calculateDamageFromHit({
326 | location: 'head',
327 | weapon: 'Long Sword',
328 | strength: 12,
329 | })
330 | );
331 | ```
332 |
333 | ## Facade(퍼사드)
334 |
335 | - 여러 클래스의 인터페이스를 감싸는 추상화된 클래스를 제공한다.
336 | - 여러 API 요청을 한번에 다루는 데 유용하게 사용할 수 있다.
337 |
338 | ```ts
339 | class BlurayPlayer {
340 | on() {
341 | console.log('Bluray player turning on...');
342 | }
343 |
344 | turnOff() {
345 | console.log('Bluray player turning off...');
346 | }
347 |
348 | play() {
349 | console.log('Playing bluray disc...');
350 | }
351 | }
352 |
353 | class Amplifier {
354 | on() {
355 | console.log('Amplifier is turning on...');
356 | }
357 |
358 | turnOff() {
359 | console.log('Amplifier turning off...');
360 | }
361 |
362 | setSource(source: string) {
363 | console.log(`Setting source to ${source}`);
364 | }
365 |
366 | setVolume(volumeLevel: number) {
367 | console.log(`Setting volume to ${volumeLevel}`);
368 | }
369 | }
370 |
371 | class Lights {
372 | dim() {
373 | console.log('Lights are dimming...');
374 | }
375 | }
376 |
377 | class TV {
378 | turnOn() {
379 | console.log('TV is turning on...');
380 | }
381 |
382 | turnOff() {
383 | console.log('TV is turning off...');
384 | }
385 | }
386 |
387 | class PopcornMaker {
388 | turnOn() {
389 | console.log('Popcorn maker is turning on...');
390 | }
391 |
392 | turnOff() {
393 | console.log('Popcorn maker is turning off...');
394 | }
395 |
396 | pop() {
397 | console.log('Popping corn!');
398 | }
399 | }
400 |
401 | class HomeTheaterFacade {
402 | private amp: Amplifier;
403 | private bluray: BlurayPlayer;
404 | private lights: Lights;
405 | private tv: TV;
406 | private popcornMaker: PopcornMaker;
407 |
408 | constructor({
409 | amp,
410 | bluray,
411 | lights,
412 | tv,
413 | popcornMaker,
414 | }: {
415 | amp: Amplifier;
416 | bluray: BlurayPlayer;
417 | lights: Lights;
418 | tv: TV;
419 | popcornMaker: PopcornMaker;
420 | }) {
421 | this.amp = amp;
422 | this.bluray = bluray;
423 | this.lights = lights;
424 | this.tv = tv;
425 | this.popcornMaker = popcornMaker;
426 | }
427 |
428 | watchMovie() {
429 | this.popcornMaker.turnOn();
430 | this.popcornMaker.pop();
431 |
432 | this.lights.dim();
433 |
434 | this.tv.turnOn();
435 |
436 | this.amp.on();
437 | this.amp.setSource('bluray');
438 | this.amp.setVolume(11);
439 |
440 | this.bluray.on();
441 | this.bluray.play();
442 | }
443 |
444 | endMovie() {
445 | this.popcornMaker.turnOff();
446 | this.amp.turnOff();
447 | this.tv.turnOff();
448 | this.bluray.turnOff();
449 | }
450 | }
451 |
452 | // ========================================================
453 |
454 | const homeTheater = new HomeTheaterFacade({
455 | amp: new Amplifier(),
456 | bluray: new BlurayPlayer(),
457 | lights: new Lights(),
458 | tv: new TV(),
459 | popcornMaker: new PopcornMaker(),
460 | });
461 |
462 | homeTheater.watchMovie();
463 | ```
464 |
465 | ## Flyweight
466 |
467 | - 많은 양의 인스턴스를 다루는 일은 많은 메모리를 소모한다. 만약 공통된 값을 공유해야 한다면 프로토타입을 이용하여 인스턴스 간의 값을 공유하도록 만들면 된다.
468 | - 보통 특정 객체의 속성을 지정할 때 생성자 안에 `this` 바인딩을 하는 일이 많겠지만, 이 패턴을 이용하고자 하면 `Prototype.Property` 방식으로 직접 프로토타입에 속성을 지정한다. 그러면 생성되는 객체는 모두 같은 속성을 공유하게 된다.
469 | - 일시적으로 특정 인스턴스에 프로토타입 값을 오버라이드 하였다가 원상복구 하고 싶다면 단순히 `delete` 를 사용하면 된다.
470 |
471 | **(코드는 단순하기 때문에 생략)**
472 |
473 | ## Proxy
474 |
475 | - 앞서 설명했던 여타 패턴과 유사하게 실제 인스턴스의 대리인 역할을 해준다.
476 | - 일반적으로 사용되는 예
477 | - 비밀 데이터의 보호
478 | - 외부 메서드 호출을 담아두기
479 | - 실제 인스턴스의 호출 전후에 추가적인 액션 삽입하기
480 | - 무거운(값나가는) 객체의 실행을 최대한 미루기
481 | - 다양한 웹 소켓 라이브러리에서 활용됨
482 |
483 | ```ts
484 | interface IResource {
485 | fetch(): void;
486 | }
487 |
488 | class ResourceProxy implements IResource {
489 | constructor(private resource: Resource) {}
490 |
491 | fetch() {
492 | console.log('invoke resource fetch method');
493 | this.resource.fetch();
494 | }
495 | }
496 |
497 | class Resource implements IResource {
498 | fetch() {
499 | console.log('fetching resource');
500 | }
501 | }
502 |
503 | const proxy = new ResourceProxy(new Resource());
504 | proxy.fetch();
505 | ```
--------------------------------------------------------------------------------
/04_구조_패턴/HWANGTAN.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Structural Patterns
4 |
5 | - Adapter
6 | - Bridge
7 | - Composite
8 | - Decorator
9 | - Facade
10 | - Flyweight
11 | - Private Class Data
12 | - Proxy
13 |
14 |
15 | ## Adapter
16 |
17 | 
18 |
19 | ```js
20 | class Line {
21 | draw(x1, y1, x2, y2) {
22 | console.log(`line from point A(${x1};${y1}), to point B(${x2};${y2})` )
23 | }
24 | }
25 |
26 | class Rectangle {
27 | draw(x1, y1, width, height) {
28 | console.log(`Rectangle with coordinate left-down point A(${x1};${y1}), width: ${width} height: ${height}` )
29 | }
30 | }
31 |
32 | class LineAdapter {
33 | constructor(line) {
34 | this.adaptee = line
35 | }
36 |
37 | draw(x1, y1, x2, y2) {
38 | this.adaptee.draw(x1, y1, x2, y2)
39 | }
40 | }
41 |
42 | class RectangleAdapter {
43 | constructor(rectangle) {
44 | this.adaptee = rectangle
45 | }
46 |
47 | draw(x1, y1, x2, y2) {
48 | const width =x2 + 10
49 | const height = y2 + 20
50 | this.adaptee.draw(x1, y1, width, height)
51 | }
52 | }
53 |
54 | class NormalAdapter {
55 | drawing() {
56 | const shapes = [new Line(), new Rectangle()]
57 | const [x1, y1, x2, y2] = [10, 20, 30, 40]
58 | const [width, height] = [50, 60]
59 | shapes.forEach(shape => {
60 | if (shape.constructor.name === 'Line')
61 | shape.draw(x1, y1, x2, y2)
62 | else if (shape.constructor.name === 'Rectangle')
63 | shape.draw(x1, y1, width, height)
64 | else
65 | throw new Error('unexpected')
66 | })
67 | }
68 | }
69 |
70 | class IndirectionAdapter {
71 | drawing() {
72 | const shapes = [
73 | new LineAdapter(new Line()),
74 | new RectangleAdapter(new Rectangle()),
75 | ]
76 | const [x1, y1, x2, y2] = [10, 20, 30, 40]
77 | shapes.forEach(shape => shape.draw(x1, y1, x2, y2))
78 | }
79 | }
80 | ```
81 |
82 | NormalAdapter는 Line 과 Rectangle 간에 인터페이스가 호환되지 않기 때문에 인자를 올바르게 넘겨야 했다.
83 | IndirectionAdapter 는 간접 레벨로 우회 할 수 있는 방법을 보여준다.
84 |
85 | 어댑터는 이전 구성 요소를 새 시스템으로 변환하거나 매핑하는 중간 추상화를 작성하는 것에 관한 것입니다. 클라이언트는 Adapter 객체에서 메서드를 호출하여 레거시 구성 요소에 대한 호출로 리디렉션합니다. 이 전략은 상속 또는 집계로 구현할 수 있습니다.
86 |
87 | 어댑터는 "래퍼 (wrapper)"로 생각할 수도 있습니다.
88 | 래퍼란 필요한 데이터를 받거나 쓰기 위해 데이터 형태를 세팅해 제공하는 서비스 이다.
89 |
90 | 어댑터는 설계된 후에 일을 처리합니다. 다리는 그들이 일하기 전에 일하게 만든다.
91 |
92 | 브리지는 추상화와 구현을 독립적으로 수행 할 수 있도록 설계되었습니다. 관련이없는 클래스가 함께 작동하도록 어댑터가 개조되었습니다.
93 |
94 | 어댑터는 주제와 다른 인터페이스를 제공합니다. 프록시는 동일한 인터페이스를 제공합니다. Decorator는 향상된 인터페이스를 제공합니다.
95 |
96 | 어댑터는 기존 개체의 인터페이스를 변경하기위한 것입니다. Decorator는 인터페이스를 변경하지 않고도 다른 객체를 향상시킵니다. 따라서 Decorator는 어댑터보다 애플리케이션에 더 투명합니다.
97 |
98 | 결과적으로 Decorator는 재귀 컴포지션을 지원하며 순수 어댑터에서는 불가능합니다.
99 |
100 | Facade는 새 인터페이스를 정의하지만 Adapter는 이전 인터페이스를 재사용합니다.
101 |
102 | 어댑터는 완전히 새로운 인터페이스를 정의하는 것과는 반대로 두 개의 기존 인터페이스를 함께 사용한다는 것을 기억하십시오.
103 |
104 | ## Bridge
105 |
106 | ```js
107 |
108 | class OldGods {
109 | prayTo(sacrifice) {
110 | console.log('oldGods')
111 | }
112 | }
113 |
114 | class OldGodsAdapter {
115 | constructor() {
116 | this._oldGods = new OldGods()
117 | }
118 |
119 | prayTo(sacrifice) {
120 | const sacrifice = new Sacrifice()
121 | this._oldGods.prayTo(sacrifice)
122 | }
123 | }
124 |
125 | class DrownedGods {
126 | prayTo(sacrifice) {
127 | console.log('DrownedGods')
128 | }
129 | }
130 |
131 | class DrownedGodsAdapter {
132 | constructor() {
133 | this._drownedGods = new DrownedGods()
134 | }
135 |
136 | prayTo(sacrifice) {
137 | const sacrifice = new HumanSacrifice()
138 | this._drownedGods.prayTo(sacrifice)
139 | }
140 | }
141 |
142 | class SevenGods {
143 | prayTo(sacrifice) {
144 | console.log('SevenGods')
145 | }
146 | }
147 |
148 | class SevenGodsAdapter {
149 | constructor() {
150 | this._prayerPurposeProvider = new PrayerPurposeProvider()
151 | this._sevenGods = new SevenGods()
152 | }
153 |
154 | prayTo(sacrifice) {
155 | const sacrifice = new PrayerPurposeProvider()
156 | this._sevenGods.prayTo(this._prayerPurposeProvider.getPerpose())
157 | }
158 | }
159 |
160 | ```
161 |
162 | ## Composite
163 |
164 | ```js
165 | class SimpleIngredient {
166 | constructor(name, calories, ironContent, vitaminCContent) {
167 | this.name = name
168 | this.calories = calories
169 | this.ironContent = ironContent
170 | this.vitaminCContent = vitaminCContent
171 | }
172 |
173 | get getName() {
174 | return this.name
175 | }
176 |
177 | get getCalories() {
178 | return this.calories
179 | }
180 |
181 | get getIronContent() {
182 | return this.ironContent
183 | }
184 |
185 | get getVitaminCContent() {
186 | return this.vitaminCContent
187 | }
188 |
189 | }
190 |
191 | class CompoundIngredient {
192 | constructor(name) {
193 | this.name = name
194 | this.ingredients = []
195 | this.ingredientReduce = this.ingredientReduce.bind(this)
196 | }
197 |
198 | addingIngredient(ingredient) {
199 | this.ingredients.push(ingredient)
200 | }
201 |
202 | ingredientReduce(target) {
203 | if (!this.ingredients.length) {
204 | return 0
205 | } else if (this.ingredients.length === 1) {
206 | return a[target]
207 | } else {
208 | const isNumber = value => Object.prototype.toString.call(value) === '[object Number]'
209 | return this.ingredients.reduce((a, b) => (isNumber(a) ? a : a[target]) + b[target])
210 | }
211 | }
212 |
213 | get getName() {
214 | return this.name
215 | }
216 |
217 | get getCalories() {
218 | const calories = 'getCalories'
219 | return this.ingredientReduce(calories)
220 | }
221 |
222 | get getIronContent() {
223 | const iron = 'getIronContent'
224 | return this.ingredientReduce(iron)
225 | }
226 |
227 | get getVitaminCContent() {
228 | const vitaminC = 'getVitaminCContent'
229 | return this.ingredientReduce(vitaminC)
230 | }
231 |
232 | }
233 |
234 | const egg = new SimpleIngredient('Egg', 155, 6, 0)
235 | const milk = new SimpleIngredient('Milk', 42, 0, 0)
236 | const sugar = new SimpleIngredient('Sugar', 387, 0, 0)
237 | const rice = new SimpleIngredient('Rice', 370, 8, 0)
238 |
239 | const ricePudding = new CompoundIngredient('Rice Pudding')
240 |
241 | ricePudding.addingIngredient(egg)
242 | ricePudding.addingIngredient(milk)
243 | ricePudding.addingIngredient(sugar)
244 | ricePudding.addingIngredient(rice)
245 |
246 | console.log(ricePudding.getCalories)
247 | ```
248 |
--------------------------------------------------------------------------------
/04_구조_패턴/JINHO.md:
--------------------------------------------------------------------------------
1 | # 4. 구조 패턴 - 현진호
2 | ## 구조 패턴 : 객체들이 상호작용할 수 있는 간단한 방법을 기술
3 | - 적응자 Adapter
4 | - 가교 Bridge
5 | - 복합체 Composite
6 | - 장식자 Decorator
7 | - 퍼사드 Facade
8 | - 플라이급 Flyweight
9 | - 프록시 Proxy
10 |
11 | ### 적응자
12 | #### 인터페이스에 완벽히 맞지 않는 클래스를 사용할 경우
13 | - 클래스에 필요한 메소드가 없는 경우
14 | - 클래스가 불필요한 메소드를 가지고 있는 경우
15 |
16 | 사용시 장점
17 | - 코드 인터페이스 단순화
18 | - 요구 사항에 맞게 인터페이스 조작
19 |
20 | ```js
21 | class Ship {
22 | SetRudderAngleTo(angle: number);
23 | SetSailConfiguration(configuration: SailConfiguration);
24 | SetSailAngle(saidId: number, sailAngle: number);
25 | GetCurrentBearing(): number;
26 | getCurrentSpeedEstimate(): number;
27 | ShiftCrewWeightTo(weightToShift: number, location: number);
28 | }
29 |
30 | class SimpleShip {
31 | TurnLeft();
32 | TurnRight();
33 | GoForward();
34 | }
35 |
36 | class ShipAdapter {
37 | constructor() {
38 | this._ship = new Ship();
39 | }
40 |
41 | TurnLeft() {
42 | this._ship.SetRudderAngleTo(-30);
43 | this._ship.SetSailAngle(3, 12);
44 | }
45 |
46 | TurnRight() {
47 | this._ship.SetRudderAngleTo(30);
48 | this._ship.SetSailAngle(5, -9);
49 | }
50 |
51 | GoForward() {
52 | // _ship에 대한 또 다른 일을 수행
53 | }
54 | }
55 |
56 | var ship = new ShipAdapter();
57 | ship.GoForward();
58 | ship.TurnLeft();
59 | ```
60 |
61 | ### 가교
62 | #### 추상과 구현의 분리
63 | 하나의 인터페이스와 다른 구현들 사이의 중간자 역할을 하는 여러 적응자를 생성
64 |
65 | 사용시 장점
66 | - 여러 다른 API들을 일관된 방식으로 다룸
67 |
68 | ```js
69 | class OldGods {
70 | prayTo(sacrifice) { console.log('We old Gods hear your prayer'); }
71 | }
72 |
73 | class DrownedGod {
74 | prayTo(humanSacrifice) { console.log('*BUBBLE* GURGLE'); }
75 | }
76 |
77 | class SevenGods {
78 | prayTo(prayerPurpose) { console.log('Sorry there are a lot of us, it gets confusing here. Did you pray for something?'); }
79 | }
80 |
81 | class OldGodsAdapter {
82 | constructor() {
83 | this._oldsGods = new OldGods();
84 | }
85 |
86 | prayTo() {
87 | var sacrifice = new Sacrifice();
88 | this._oldsGods.prayTo(sacrifice);
89 | }
90 | }
91 |
92 | class DrownedGodAdapter {
93 | constructor() {
94 | this._drownedGod = new DrownedGod();
95 | }
96 |
97 | prayTo() {
98 | var sacrifice = new HumanSacrifice();
99 | this._drownedGod.prayTo(sacrifice);
100 | }
101 | }
102 |
103 | class SevenGodsAdapter {
104 | constructor() {
105 | this.prayerPurposeProvider = new PrayerPurposeProvider();
106 | this._sevenGods = new SevenGods();
107 | }
108 |
109 | prayTo() {
110 | this._sevenGods.prayTo(this.prayerPurposeProvider.GetPurpose());
111 | }
112 | }
113 |
114 | var god1 = new SevenGodsAdapter();
115 | var god2 = new DrownedGodAdapter();
116 | var god3 = new OldGodsAdapter();
117 |
118 | var gods = [god1, god2, god3];
119 | for (var i = 0; i < gods.length; i++) {
120 | gods[i].prayTo();
121 | }
122 | ```
123 |
124 | ### 복합체
125 | #### 객체들 간의 강한 결합(상속 같은)을 회피
126 |
127 | 바로 jQuery가 복합체 패턴을 따릅니다. jQuery는 하나의 태그를 선택하든, 여러 개의 태그를 동시에 선택하든 모두 같은 메소드를 쓸 수 있습니다. 예를 들면, $('#zero')로 하나의 태그를 선택할 수도 있고, $('p')로 모든 p 태그를 선택할 수도 있습니다. 하지만 개수와 상관 없이 모두 attr이나 css같은 메소드를 사용할 수 있습니다.
128 |
129 | 복합체 구성 방법
130 | - 복합체 컴포넌트가 고정된 개수의 다양한 컴포넌트로 구성
131 | - 정해지지 않은 개수의 컴포넌트로 구축
132 |
133 | ```js
134 | class SimpleIngredient {
135 | constructor(name, calories, ironContent, vitaminCContent) {
136 | this.name = name;
137 | this.calories = calories;
138 | this.ironContent = ironContent;
139 | this.vitaminCContent = vitaminCContent;
140 | }
141 |
142 | GetName() { return this.name; }
143 | GetCalories() { return this.calories; }
144 | GetIronContent() { return this.ironContent; }
145 | GetVitaminCContent() { return this.vitaminCContent; }
146 | }
147 |
148 | class CompoundIngredient {
149 | constructor(name) {
150 | this.name = name;
151 | this.ingredients = new Array();
152 | }
153 |
154 | AddIngredient(ingredient) {
155 | this.ingredients.push(ingredient);
156 | }
157 |
158 | GetName() { return this.name; }
159 |
160 | GetCalories() {
161 | var total = 0;
162 | for (var i = 0 ; i < this.ingredients.length; i++) {
163 | total += this.ingredients[i].GetCalories();
164 | }
165 | return total;
166 | }
167 |
168 | GetIronContent() {
169 | var total = 0;
170 | for (var i = 0 ; i < this.ingredients.length; i++) {
171 | total += this.ingredients[i].GetIronContent();
172 | }
173 | return total;
174 | }
175 |
176 | GetVitaminCContent() {
177 | var total = 0;
178 | for (var i = 0 ; i < this.ingredients.length; i++) {
179 | total += this.ingredients[i].GetVitaminCContent();
180 | }
181 | return total;
182 | }
183 | }
184 |
185 | var egg = new SimpleIngredient('Egg', 155, 6, 0);
186 | var milk = new SimpleIngredient('Milk', 42, 0, 0);
187 | var sugar = new SimpleIngredient('Sugar', 387, 0, 0);
188 | var rice = new SimpleIngredient('Rice', 370, 8, 0);
189 |
190 | var ricePudding = new CompoundIngredient('Rice Pudding');
191 | ricePudding.AddIngredient(egg);
192 | ricePudding.AddIngredient(milk);
193 | ricePudding.AddIngredient(sugar);
194 | ricePudding.AddIngredient(rice);
195 |
196 | console.log(ricePudding.GetCalories() + ' calories');
197 |
198 | ```
199 |
200 |
201 | ### 장식자
202 | #### 적은 클래스를 정의하면서 여러 기능을 무한대로 혼합하여 사용할 수 있게 함
203 |
204 | ```js
205 | class BasicArmor {
206 | CalculateDamageFromHit(hit) { return 1; }
207 |
208 | GetArmorIntegrity() { return 1; }
209 | }
210 |
211 | class ChainMail {
212 | constructor(decoratedArmor) {
213 | this.decoratedArmor = decoratedArmor;
214 | }
215 |
216 | CalculateDamageFromHit(hit) {
217 | hit.strength = hit.strength * .8;
218 | return this.decoratedArmor.CalculateDamageFromHit(hit);
219 | }
220 |
221 | GetArmorIntegrity() {
222 | return .9 * this.decoratedArmor.GetArmorIntegrity();
223 | }
224 | }
225 |
226 | var basicArmor = new BasicArmor();
227 | var chainMail = new ChainMail(new BasicArmor());
228 |
229 | console.log(chainMail.CalculateDamageFromHit({strength: 5})); // 4
230 | console.log(chainMail.GetArmorIntegrity()); // 0.9
231 |
232 | ```
233 |
234 | ### 퍼사드
235 | #### 복잡하고 세부적인 것들은 감추고 간단한 것만 보여줌
236 | 다수의 서브 시스템을 사용하기 쉽게 서비스를 제공
237 |
238 | ```js
239 | class Legion {
240 | supply() {}
241 | makeFormation() {}
242 | goForward() {}
243 | pullOutSword() {}
244 | runToEnemy() {}
245 | retreat() {}
246 | }
247 |
248 | class Galba {
249 | constructor() {
250 | this.legions = [];
251 | this.legions.push(new Legion(1));
252 | this.legions.push(new Legion(2));
253 | this.legions.push(new Legion(3));
254 | }
255 |
256 | march() {
257 | this.legions.forEach(function(legion) {
258 | legion.supply();
259 | legion.makeFormation();
260 | legion.goForward();
261 | });
262 | }
263 |
264 | attach() {
265 | this.legions.forEach(function(legion) {
266 | legion.makeFormation();
267 | legion.pullOutSword();
268 | legion.runToEnemy();
269 | });
270 | }
271 |
272 | halt() {
273 | this.legions.forEach(function(legion) {
274 | legion.halt();
275 | });
276 | }
277 |
278 | retreat() {
279 | this.legions.forEach(function(legion) {
280 | legion.retreat();
281 | });
282 | }
283 | }
284 |
285 | ```
286 |
287 | ### 플라이급
288 | #### 객체의 인스턴스 개수는 많지만 인스턴스의 변화는 아주 작은 경우
289 | 대부분의 인스턴스가 동일한 값을 가지고 있어, 프로토타입의 다른 값들만 관리
290 |
291 | ```js
292 | /*
293 | class Soldier {
294 | constructor() {
295 | this.Health = 10;
296 | this.FightingAbility = 5;
297 | this.Hunger = 0;
298 | }
299 | }
300 | */
301 |
302 | class Soldier {}
303 |
304 | Soldier.prototype.Health = 10;
305 | Soldier.prototype.FightingAbility = 5;
306 | Soldier.prototype.Hunger = 0;
307 |
308 | var soldier1 = new Soldier();
309 | var soldier2 = new Soldier();
310 | console.log(soldier1.Health); // 10
311 | soldier1.Health = 7;
312 | console.log(soldier1.Health); // 7
313 | console.log(soldier2.Health); // 10
314 | delete soldier1.Health;
315 | console.log(soldier1.Health); // 10
316 | ```
317 |
318 | ### 프록시
319 | #### 사용자가 원하는 행동을 하기 전에 한번 거쳐가는 단계
320 |
321 | 실제 인스턴스의 인터페이스를 미러링하여 값비싼 객체의 생성,사용을 제어
322 |
323 | 사용 목적
324 | - 내부 인스턴스 확인, 생성되지 않은 경우 메소드 호출 시 이를 생성하여 전달
325 | - 보안을 염두에 두지 않고 설계된 클랫의 보안이 필요한 경우
326 | - 메소드 호출에 추가 기능 삽입 시
327 |
328 | ```js
329 | class BarrelCalculator {
330 | calculateNumberNeeded(volume) {
331 | return Math.ceil(volume / 357);
332 | }
333 | }
334 |
335 | class DragonBarrelCalculator {
336 | calculateNumberNeeded(volume) {
337 | if (this._barrelCalculator == null) {
338 | this._barrelCalculator = new BarrelCalculator();
339 | return this._barrelCalculator.calculateNumberNeeded(volume * .77);
340 | }
341 | }
342 | }
343 | ```
344 |
--------------------------------------------------------------------------------
/04_구조_패턴/README.md:
--------------------------------------------------------------------------------
1 | # 4. 구조 패턴
2 |
3 | [이창규](./ARUSANTIMO.md)
4 |
5 | [황원준](./HWANGTAN.md)
6 |
7 | [정재남](./jaenam.md)
8 |
9 | [현진호](./JINHO.md)
10 |
11 | [안도형](./DOHYUNG.md)
12 |
--------------------------------------------------------------------------------
/04_구조_패턴/jaenam.md:
--------------------------------------------------------------------------------
1 | ## Adapter pattern
2 |
3 | [es6 design patterns](http://loredanacirstea.github.io/es6-design-patterns)
4 |
5 | ```js
6 | class Duck {
7 | quack () {
8 | console.log('꽥')
9 | }
10 | fly () {
11 | console.log("나는듯 안나는듯 날고있어!")
12 | }
13 | }
14 | class Sparrow {
15 | twitter () {
16 | console.log('짹짹')
17 | }
18 | fly () {
19 | console.log('누구보다 가볍게 남들보다 빠르게 날아가는 바람위의 나그네!')
20 | }
21 | }
22 |
23 | class SparrowDuckAdapter {
24 | constructor (sparrow) {
25 | this.sparrow = sparrow
26 | }
27 | quack () {
28 | return this.sparrow.twitter()
29 | }
30 | fly () {
31 | return this.sparrow.fly()
32 | }
33 | }
34 |
35 | const duck = new Duck()
36 | const sparrow = new SparrowDuckAdapter(new Sparrow())
37 |
38 | duck.quack()
39 | duck.fly()
40 | sparrow.quack()
41 | sparrow.fly()
42 | ```
43 |
44 | ## Bridge pattern
45 |
46 | ```js
47 | class AbstractDrawingAPI {
48 | drawCircle (x, y, r) { }
49 | }
50 | class DrawingAPI1 extends AbstractDrawingAPI {
51 | drawCircle (x, y, r) {
52 | console.log(`API1.circle at ${x}:${y} radius ${r}`);
53 | }
54 | }
55 | class DrawingAPI2 extends AbstractDrawingAPI {
56 | drawCircle (x, y, r) {
57 | console.log(`API2.circle at ${x}:${y} radius ${r}`);
58 | }
59 | }
60 |
61 | class Shape {
62 | draw () { }
63 | resizeByPercentage (pct) { }
64 | }
65 | class CircleShape extends Shape {
66 | constructor (x, y, r, drawingAPI) {
67 | super();
68 | this.x = x
69 | this.y = y
70 | this.radius = r
71 | this.drawingAPI = drawingAPI
72 | }
73 | draw () {
74 | this.drawingAPI.drawCircle(this.x, this.y, this.radius)
75 | }
76 | resizeByPercentage (pct) {
77 | this.radius *= pct
78 | }
79 | }
80 | const shapes = [
81 | new CircleShape(1, 2, 3, new DrawingAPI1()),
82 | new CircleShape(5, 7, 11, new DrawingAPI2()),
83 | ]
84 | shapes.forEach(shape => {
85 | shape.draw()
86 | })
87 | shapes.forEach(shape => {
88 | shape.resizeByPercentage(2.5)
89 | shape.draw()
90 | })
91 | ```
92 |
93 | ## Composite pattern
94 |
95 | ```js
96 | class Graphic {
97 | print () { }
98 | }
99 | class CompositeGraphic extends Graphic {
100 | constructor () {
101 | super()
102 | this.childGraphics = new Set()
103 | }
104 | print () {
105 | this.childGraphics.forEach(graphic => { graphic.print() })
106 | }
107 | add (graphic) {
108 | this.childGraphics.add(graphic)
109 | }
110 | remove (graphic) {
111 | this.childGraphics.delete(graphic)
112 | }
113 | }
114 | class Ellipse extends Graphic {
115 | print () {
116 | console.log('Ellipse')
117 | }
118 | }
119 | const ellipse1 = new Ellipse()
120 | const ellipse2 = new Ellipse()
121 | const ellipse3 = new Ellipse()
122 | const ellipse4 = new Ellipse()
123 | const graphic1 = new CompositeGraphic()
124 | const graphic2 = new CompositeGraphic()
125 | const graphic3 = new CompositeGraphic()
126 |
127 | graphic2.add(ellipse1)
128 | graphic2.add(ellipse2)
129 | graphic2.add(ellipse3)
130 | graphic3.add(ellipse4)
131 |
132 | graphic1.add(graphic2)
133 | graphic1.add(graphic3)
134 |
135 | graphic1.print()
136 | ```
137 |
138 | ## Decorator pattern
139 |
140 | ```js
141 | class Component {
142 | Operation () {}
143 | }
144 | class ConcreteComponent extends Component {
145 | constructor () {
146 | super()
147 | console.log('ConcreteComponent created')
148 | }
149 | Operation () {
150 | console.log('component operation')
151 | }
152 | }
153 | class Decorator extends Component {
154 | constructor (component) {
155 | super()
156 | this.component = component
157 | console.log('Decorator created')
158 | }
159 | Operation () {
160 | this.component.Operation()
161 | }
162 | }
163 |
164 | class ConcreteDecoratorA extends Decorator {
165 | constructor (component, sign) {
166 | super(component)
167 | this.addedState = sign
168 | console.log('ConcreteDecoratorA created')
169 | }
170 | Operation () {
171 | super.Operation()
172 | console.log(this.addedState)
173 | }
174 | }
175 | class ConcreteDecoratorB extends Decorator {
176 | constructor (component, sign) {
177 | super(component)
178 | this.addedState = sign
179 | console.log('ConcreteDecoratorB created')
180 | }
181 | Operation () {
182 | super.Operation()
183 | console.log(this.addedState)
184 | }
185 | AddedBehavior () {
186 | this.Operation()
187 | console.log('decoratorB addedBehavior')
188 | }
189 | }
190 |
191 | const component = new ConcreteComponent()
192 | const decoratorA = new ConcreteDecoratorA(component, 'decoratorA added sign')
193 | const decoratorB = new ConcreteDecoratorB(component, 'decoratorB added sign')
194 | component.Operation()
195 | decoratorA.Operation()
196 | decoratorB.AddedBehavior()
197 | ```
198 |
199 | ## Facade pattern
200 |
201 | ```js
202 | class CPU {
203 | freeze () {}
204 | jump (position) {}
205 | execute () {}
206 | }
207 | class Memory {
208 | load (position, data) {}
209 | }
210 | class HardDrive {
211 | read (lba, size) {}
212 | }
213 |
214 | class FacadeComputer {
215 | startComputer () {
216 | const cpu = new CPU()
217 | const memory = new Memory()
218 | const hard = new HardDrive()
219 | cpu.freeze()
220 | memory.load(BOOT_ADDRESS, hard.read(BOOT_SECTOR, SECTOR_SIZE))
221 | cpu.jump(BOOT_ADDRESS)
222 | cpu.execute()
223 | }
224 | }
225 |
226 | const computer = new FacadeComputer()
227 | computer.startComputer()
228 | ```
229 |
230 | ```js
231 | class PatternTest {
232 | constructor () {
233 | this.htmlid = null
234 | this.log("PatternTest class created")
235 | }
236 | log (text) {
237 | if(this.htmlid === null){
238 | console.log(text)
239 | } else{
240 | document.getElementById(this.htmlid).innerHTML += text+''
241 | }
242 | }
243 | init (id) {
244 | this.htmlid = id
245 | document.body.innerHTML += `
`
246 | }
247 | test (dp) {
248 | if(this[dp]) {
249 | this[dp]()
250 | } else {
251 | this.log('nothing to test')
252 | }
253 | }
254 |
255 | Facade () {
256 | this.init('test_Facade')
257 | this.log("This is the Facade")
258 | }
259 | AbstractFactory () {
260 | this.init('test_AbstractFactory')
261 | this.log("This is the Abstact Factory")
262 | }
263 | Builder () {
264 | this.init('test_Builder')
265 | this.log("This is the Builder")
266 | }
267 | Factory () {
268 | this.init('test_Factory')
269 | this.log("This is the Factory")
270 | }
271 | }
272 | const test = new PatternTest()
273 | test.test('Facade')
274 | test.test('AbstractFactory')
275 | ```
276 |
277 |
278 | ## Flyweight
279 |
280 | ```js
281 | const FontData = (() => {
282 | const KEY = Symbol('key')
283 | const flyweightData = new Map()
284 |
285 | class Font {
286 | constructor (key) {
287 | this[KEY] = key
288 | }
289 | getData () {
290 | return flyweightData.get(this[KEY])
291 | }
292 | }
293 | return class {
294 | constructor (pointSize, fontFace, color) {
295 | this.pointSize = pointSize
296 | this.fontFace = fontFace
297 | this.color = color
298 | }
299 | buildKey () {
300 | return `${this.pointSize}_${this.fontFace}_${this.color}`
301 | }
302 | static create (pointSize, fontFace, color) {
303 | const fontData = new FontData(pointSize, fontFace, color)
304 | const key = fontData.buildKey()
305 | if(flyweightData.has(key)) {
306 | flyweightData.get(key)
307 | } else {
308 | flyweightData.set(key, fontData)
309 | }
310 | return new Font(key)
311 | }
312 | static getFontList () {
313 | return [...flyweightData]
314 | }
315 | }
316 | })()
317 |
318 | const fontA = FontData.create(15, 'Nanum Gothic', 'black')
319 | const fontB = FontData.create(13, 'Nanum Gothic', 'black')
320 | const fontC = FontData.create(12, 'Nanum Myungjo', 'white')
321 | const fontD = FontData.create(13, 'Nanum Gothic', 'black')
322 | const fontE = FontData.create(12, 'Nanum Myungjo', 'red')
323 | const fontF = FontData.create(15, 'Nanum Gothic', 'black')
324 |
325 | console.log(fontA, fontB, fontC, fontD, fontE, fontF)
326 | console.log(FontData.getFontList())
327 | console.log(fontA.getData())
328 | console.log(fontB.getData())
329 | ```
330 |
331 | ## Proxy pattern
332 |
333 | ```js
334 | class RealImage {
335 | constructor (filename) {
336 | this.filename = filename
337 | this.loadImageFromDisk()
338 | }
339 | loadImageFromDisk () {
340 | console.log(`Loading ${this.filename}`)
341 | }
342 | displayImage () {
343 | console.log(`Displaying ${this.filename}`)
344 | }
345 | }
346 |
347 | class ProxyImage {
348 | constructor (filename) {
349 | this.filename = filename
350 | }
351 | displayImage () {
352 | const image = new RealImage(this.filename)
353 | image.displayImage()
354 | }
355 | }
356 | const img1 = new ProxyImage('HiRes_10MB_Photo1.png')
357 | const img2 = new ProxyImage('HiRes_30MB_Photo2.png')
358 | img1.displayImage()
359 | img2.displayImage()
360 | ```
361 |
--------------------------------------------------------------------------------
/05_행동_패턴/BHKIM.md:
--------------------------------------------------------------------------------
1 | # Chapter5. 행동 패턴 (Behavioral)
2 |
3 | ### 책임 연쇄 (Chain of Responsibility)
4 | 객체 지향 디자인에서 chain-of-responsibility pattern은 명령 객체와 일련의 처리 객체를 포함하는 디자인 패턴이다. 각각의 처리 객체는 명령 객체를 처리할 수 있는 연산의 집합이고, 체인 안의 처리 객체가 핸들할 수 없는 명령은 다음 처리 객체로 넘겨진다. 이 작동방식은 새로운 처리 객체부터 체인의 끝까지 다시 반복된다.
5 | 이 패턴은 결합을 느슨하게 하기 위해 고안되었으며 가장 좋은 프로그래밍 사례로 꼽힌다.
6 |
7 | ### 명령 (Command)
8 | 커맨드 패턴(Command pattern)이란 요청을 객체의 형태로 캡슐화하여 사용자가 보낸 요청을 나중에 이용할 수 있도록 매서드 이름, 매개변수 등 요청에 필요한 정보를 저장 또는 로깅, 취소할 수 있게 하는 패턴이다.
9 | ```js
10 | // wiki
11 | class Switch {
12 | constructor() {
13 | this._commands = [];
14 | }
15 | storeAndExecute(command) {
16 | this._commands.push(command);
17 | command.execute();
18 | }
19 | }
20 | class Light {
21 | turnOn() { console.log('turn on') }
22 | turnOff() { console.log('turn off') }
23 | }
24 | class FlipDownCommand {
25 | constructor(light) {
26 | this._light = light;
27 | }
28 |
29 | execute() {
30 | this._light.turnOff();
31 | }
32 | }
33 | class FlipUpCommand {
34 | constructor(light) {
35 | this._light = light;
36 | }
37 | execute() {
38 | this._light.turnOn();
39 | }
40 | }
41 | var light = new Light();
42 | var switchUp = new FlipUpCommand(light);
43 | var switchDown = new FlipDownCommand(light);
44 | var s = new Switch();
45 | s.storeAndExecute(switchUp);
46 | s.storeAndExecute(switchDown);
47 | ```
48 |
49 | ### 해석자 (Interpreter)
50 | 컴퓨터 프로그래밍에서 인터프리터 패턴(interpreter pattern)은 한 언어에서 문들을 평가하는 방법을 규정하는 디자인 패턴이다. 기본 개념은 특화된 컴퓨터 언어에서 각 기호(종단 또는 비종단)마다 클래스를 갖는 것이다. 언어 내 한 문의 구문 트리는 컴포지트 패턴의 인스턴스이며 클라이언트의 문을 평가(해석)하기 위해 사용된다.
51 | ```js
52 | // sample code
53 | class Battle {
54 | constructor(battleGround, agressor, defender, victor) {
55 | this.battleGround = battleGround;
56 | this.agressor = agressor;
57 | this.defender = defender;
58 | this.victor = victor;
59 | }
60 | }
61 | class Parser {
62 | constructor(battleText) {
63 | this.battleText = battleText;
64 | this.currentIndex = 0;
65 | this.battleList = battleText.split("\n");
66 | }
67 | nextBattle() {
68 | if (!this.battleList[0])
69 | return null;
70 | var segments = this.battleList[0].match(/\((.+?)\s?->\s?(.+?)\s?<-\s?(.+?)\s?->\s?(.+)/);
71 | return new Battle(segments[2], segments[1], segments[3], segments[4]);
72 | }
73 | }
74 | const text = "(Robert Baratheon -> Revier Trident <- RhaegarTargaryen) -> Robert Baratheon";
75 | const p = new Parser(text);
76 | console.log(p.nextBattle());
77 | ```
78 |
79 | ### 반복자 (Iterator)
80 | 반복자 패턴(iterator pattern)은 객체 지향 프로그래밍에서 반복자를 사용하여 컨테이너를 가로지르며 컨테이너의 요소들에 접근하는 디자인 패턴이다. 반복자 패턴은 컨테이너로부터 알고리즘을 분리시키며, 일부의 경우 알고리즘들은 필수적으로 컨테이너에 특화되어 있기 때문에 분리가 불가능하다.
81 | ```js
82 | // sample code
83 | // Fibonacci number
84 | // 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144,...
85 | class FibonacciIterator {
86 | constructor() {
87 | this.previous = 1;
88 | this.beforePrevious = 1;
89 | }
90 | next() {
91 | var current = this.previous + this.beforePrevious;
92 | this.beforePrevious = this.previous;
93 | this.previous = current;
94 | return current;
95 | }
96 | }
97 | const fib = new FibonacciIterator();
98 | console.log(fib.next());
99 | console.log(fib.next());
100 | console.log(fib.next());
101 | console.log(fib.next());
102 | console.log(fib.next());
103 | ```
104 |
105 | ### 중재자 (Mediator)
106 | 중자재 패턴(mediator pattern), 조정자 패턴은 소프트웨어 공학에서 어떻게 객체들의 집합이 상호작용하는지를 함축해놓은 객체를 정의한다. 이 패턴은 프로그램의 실행 행위를 변경할 수 있기 때문에 행위 패턴으로 간주된다.
107 | 중재자 패턴을 사용하면 객체 간 통신은 중자재 객체 안에 함축된다. 객체들은 더 이상 다른 객체와 서로 직접 통신하지 않으며 대신 중재자를 통해 통신한다. 이를 통해 통신 객체 간 의존성을 줄일 수 있으므로 결합도를 감소시킨다.
108 |
109 | ### 메멘토 (Memento)
110 | 메멘토 패턴(memento pattern)은 객체를 이전 상태로 되돌릴 수 있는 기능을 제공하는 소프트웨어 디자인 패턴이다. (롤백을 통한 실행 취소)
111 |
112 | ### 감시자 (Observer)
113 | 옵서버 패턴(observer pattern)은 객체의 상태 변화를 관찰하는 관찰자들, 즉 옵저버들의 목록을 객체에 등록하여 상태 변화가 있을 때마다 메서드 등을 통해 객체가 직접 목록의 각 옵저버에게 통지하도록 하는 디자인 패턴이다. 주로 분산 이벤트 핸들링 시스템을 구현하는 데 사용된다. 발행/구독 모델로 알려져 있기도 하다.
114 | ```js
115 | // sample code
116 | class Spy {
117 | constructor() {
118 | this._partiesToNotify = [];
119 | }
120 | Subscribe(subscriber) {
121 | this._partiesToNotify.push(subscriber);
122 | }
123 | Unsubscribe(subscriber) {
124 | this._partiesToNotify.remove(subscriber);
125 | }
126 | SetPainKillers(painKillers) {
127 | this._painKillers = painKillers;
128 | for (var i = 0; i < this._partiesToNotify.length; i++) {
129 | this._partiesToNotify[i](painKillers);
130 | }
131 | }
132 | }
133 | class Player {
134 | OnKingPainKillerChange(newPainKillerAmount) {
135 | //perform some action
136 | console.log(newPainKillerAmount);
137 | }
138 | }
139 | const s = new Spy();
140 | const p = new Player();
141 | s.Subscribe(p.OnKingPainKillerChange);
142 | s.SetPainKillers(12);
143 | ```
144 |
145 | ### 상태 (State)
146 | 상태 패턴(state pattern)은 객체 지향 방식으로 상태 기계를 구현하는 행위 소프트웨어 디자인 패턴이다. 상태 패턴을 이용하면 상태 패턴 인터페이스의 파생 클래스로서 각각의 상태를 구현함으로써, 또 패턴의 슈퍼클래스에 의해 정의되는 메소드를 호출하여 상태 변화를 구현함으로써 상태 기계를 구현한다.
147 | 상태 패턴은 패턴의 인터페이스에 정의된 메소드들의 호출을 통해 현재의 전략을 전환할 수 있는 전략 패턴으로 해석할 수 있다.
148 |
149 | ### 전략 (Strategy)
150 | 전략 패턴(strategy pattern) 또는 정책 패턴(policy pattern)은 실행 중에 알고리즘을 선택할 수 있게 하는 행위 소프트웨어 디자인 패턴이다. 전략 패턴은 특정한 계열의 알고리즘들을 정의하고 각 알고리즘을 캡슐화하며 이 알고리즘들을 해당 계열 안에서 상호 교체가 가능하게 만든다.
151 | ```js
152 | // sample code
153 | class TravelResult {
154 | constructor(durationInDays, probabilityOfDeath, cost) {
155 | this.durationInDays = durationInDays;
156 | this.probabilityOfDeath = probabilityOfDeath;
157 | this.cost = cost;
158 | }
159 | }
160 | class SeaGoingVessel {
161 | Travel(source, destination) {
162 | return new TravelResult(15, .25, 500);
163 | }
164 | }
165 | class Horse {
166 | Travel(source, destination) {
167 | return new TravelResult(30, .25, 50);
168 | }
169 | }
170 | class Walk {
171 | Travel(source, destination) {
172 | return new TravelResult(150, .55, 0);
173 | }
174 | }
175 | const currentMoney = 70;
176 | let strat;
177 | if (currentMoney > 500)
178 | strat = new SeaGoingVessel();
179 | else if (currentMoney > 50)
180 | strat = new Horse();
181 | else
182 | strat = new Walk();
183 | const travelResult = strat.Travel();
184 | console.log(travelResult);
185 | ```
186 |
187 | ### 템플릿 메소드 (Template method)
188 |
189 | ### 방문자 (Visitor)
190 | 객체 지향 프로그래밍과 소프트웨어 공학에서 비지터 패턴(visitor pattern; 방문자 패턴)은 알고리즘을 객체 구조에서 분리시키는 디자인 패턴이다. 이렇게 분리를 하면 구조를 수정하지 않고도 실질적으로 새로운 동작을 기존의 객체 구조에 추가할 수 있게 된다. 개방-폐쇄 원칙을 적용하는 방법의 하나이다.
191 |
--------------------------------------------------------------------------------
/05_행동_패턴/DOHYUNG.md:
--------------------------------------------------------------------------------
1 | # 5장 - 행동 패턴
2 |
3 | - Gist - [Mastering Javascript Design Pattern Chapter 05 · GitHub](https://gist.github.com/emaren84/cbc09144a236270e5bd5c1d076aa9d63)
4 | - 공부 중 찾은 참고 사이트(안타깝게도 JS로 된 코드는 없다) - [Design Patterns](https://sourcemaking.com/design_patterns)
5 |
6 | ## Command
7 |
8 | - 현재 상태와 호출되어야 하는 메서드를 캡슐화하는 패턴
9 | - 명령을 내리면서 Undo, Logging을 위해 다른 큐에 담아두는 등의 동작이 가능하다.
10 | - 자바스크립트의 함수는 일급 객체이기 때문에 패러매터로 전달이 가능해서 비교적 쉽게 구현할 수 있다.
11 | - 보통은 적확한 패러매터를 받을 수 있도록 클래스를 구성한다. 그리고 그 생성자에서 필요한 인자를 받는다. 인자가 제대로 들어왔는지 확인하고 제대로 들어오지 않았을 시 바로 실패했다고 알려주는 것이 나중에 실행할 때 문제가 생기는 것 보다 낫다.
12 | - 이 패턴을 이용하여 사용할 명령을 미리 담아두고 호출자가 언제든지 미뤄진 시간에 호출하게 만들 수 있다.
13 |
14 | ```typescript
15 | interface ICommand {
16 | execute(): void;
17 | }
18 |
19 | class ConcreteCommander1 implements ICommand {
20 | constructor(private receiver: Receiver) {}
21 |
22 | execute(): void {
23 | console.log("`execute` method of ConcreteCommand1 is being called!");
24 | this.receiver.action();
25 | }
26 | }
27 |
28 | class ConcreteCommander2 implements ICommand {
29 | constructor(private receiver: Receiver) {}
30 |
31 | execute(): void {
32 | console.log("`execute` method of ConcreteCommand2 is being called!");
33 | this.receiver.action();
34 | }
35 | }
36 |
37 | class Invoker {
38 | private commands: ICommand[] = [];
39 |
40 | storeAndExecute(cmd: ICommand) {
41 | this.commands.push(cmd);
42 | cmd.execute();
43 | }
44 | }
45 |
46 | class Receiver {
47 | action(): void {
48 | console.log("action is being called!");
49 | }
50 | }
51 |
52 | const receiver: Receiver = new Receiver(),
53 | command1: ICommand = new ConcreteCommander1(receiver),
54 | command2: ICommand = new ConcreteCommander2(receiver),
55 | invoker: Invoker = new Invoker();
56 |
57 | invoker.storeAndExecute(command1);
58 | invoker.storeAndExecute(command2);
59 | ```
60 |
61 | ## Interpreter
62 |
63 | - DSL(Domain Specific Language)를 만드는 패턴
64 | - 상대적으로 중요도가 떨어져서 건너뜀
65 |
66 | ## Iterator
67 |
68 | - 단순히 배열이나 객체를 제외한 인스턴스에도 내부 요소를 반복 가능하도록 인터페이스를 구현하는 패턴.
69 | - 보통은 `next` 메서드를 만들어 다음 요소를 꺼내는 동작을 실행하고, 더 꺼낼 요소가 없을 시 별도의 에러를 일으키기도 하고, `isDone` 등의 getter로 모든 요소를 꺼냈는지도 구현할 수 있다.
70 | - ES6에서 이터레이터가 구현되어있기 때문에 실제 코드는 건너뜀
71 |
72 | ## Mediator
73 |
74 | - 여러 객체를 한 곳에서 통제하고자 할때 사용하는 패턴. 각각의 객체를 통제하는 것보다 유지보수하기 용이하다.
75 | - 보통 Observer(옵저버) 패턴과 비슷하게 여겨지는데, 옵저버 패턴은 데이터의 변경, 업데이트 시 여러 객체에게 한번에 상태를 알리는데 초점이 맞추어져 있다면 중재자 패턴은 ‘생성’ 시 행위에 초점이 맞추어져 있다.
76 | - [The Mediator Pattern in JavaScript](http://jarrettmeyer.com/2016/04/21/mediator-pattern-in-javascript)
77 |
78 | ```typescript
79 | interface ITempHandler {
80 | canHandle(msg: ITemperature): boolean;
81 | handle(msg: ITemperature): ITempMessage;
82 | }
83 |
84 | interface ITemperature {
85 | city: string;
86 | temp: number;
87 | }
88 |
89 | interface ITempMessage {
90 | temp: number;
91 | message: string;
92 | }
93 |
94 | class TempMediator {
95 | private handlers: ITempHandler[] = [];
96 |
97 | addHandler(handler: ITempHandler) {
98 | this.handlers.push(handler);
99 | }
100 |
101 | request(message: ITemperature) {
102 | return this.handlers
103 | .filter(handler => handler.canHandle(message))
104 | .map(handler => handler.handle(message));
105 | }
106 | }
107 |
108 | const tooColdHandler: ITempHandler = {
109 | canHandle(message) {
110 | return message.temp < 10;
111 | },
112 | handle(message) {
113 | return {
114 | temp: message.temp,
115 | message: `In ${message.city}, It is too cold!`
116 | };
117 | }
118 | };
119 |
120 | const tooHotHandler: ITempHandler = {
121 | canHandle(message) {
122 | return 30 <= message.temp;
123 | },
124 | handle(message) {
125 | return {
126 | temp: message.temp,
127 | message: `In ${message.city}, It is too hot!`
128 | };
129 | }
130 | };
131 |
132 | const niceDayHandler: ITempHandler = {
133 | canHandle(message) {
134 | return 15 <= message.temp && message.temp < 25;
135 | },
136 | handle(message) {
137 | return {
138 | temp: message.temp,
139 | message: `In ${message.city}, It should be a pleasant day today!`
140 | };
141 | }
142 | };
143 |
144 | const mediator = new TempMediator();
145 | mediator.addHandler(tooColdHandler);
146 | mediator.addHandler(tooHotHandler);
147 | mediator.addHandler(niceDayHandler);
148 |
149 | console.log(mediator.request({ city: 'Seoul', temp: 9 }));
150 | console.log(mediator.request({ city: 'Busan', temp: 20 }));
151 | console.log(mediator.request({ city: 'Daegu', temp: 35 }));
152 | ```
153 |
154 | ## Memento
155 |
156 | - 객체의 상태를 변경 전 상태로 돌릴 수 있는 인터페이스를 구현하는 패턴
157 | - Memento(상태 저장) <-> Originator(Memento 생성) <-> Caretaker(Originator 에게 상태 복원을 요청)
158 | - Copy on Write 로 알려져 있음
159 | - 현재 클라이언트 사이드(브라우저)에서는 Undo 기능이 그렇게 요구되지 않으나, 데스크탑에서는 충분히 많이 요구되고 있다. -> 그리고 현재는 브라우저에서도 기능이 복잡한 앱의 경우 충분히 Undo 기능을 이런 방식을 활용하여 구현할 수 있을 것이다.
160 |
161 | ```typescript
162 | interface WorldState {
163 | numberOfKings: number;
164 | currentKingInKingsLanding: string;
165 | season: string;
166 | }
167 |
168 | class WorldStateProvider {
169 | private numberOfKings: number;
170 | private currentKingInKingsLanding: string;
171 | private season: string;
172 |
173 | setState(newState: {
174 | numberOfKings?: number;
175 | currentKingInKingsLanding?: string;
176 | season?: string;
177 | }) {
178 | if (Object.keys(newState).length <= 0) {
179 | throw new Error('There is no information to set the state');
180 | }
181 |
182 | for (const prop in newState) {
183 | this[prop] = newState[prop];
184 | }
185 | }
186 |
187 | saveMemento(): WorldState {
188 | return {
189 | numberOfKings: this.numberOfKings,
190 | currentKingInKingsLanding: this.currentKingInKingsLanding,
191 | season: this.season
192 | };
193 | }
194 |
195 | restoreMemento(memento: WorldState) {
196 | this.setState(memento);
197 | }
198 | }
199 |
200 | class SoothSayer {
201 | private startingPoints: WorldState[] = [];
202 | private currentState: WorldStateProvider = new WorldStateProvider();
203 |
204 | setInitialConditions(state: WorldState) {
205 | this.currentState.setState(state);
206 | }
207 |
208 | storeMemento() {
209 | const currentMemento = this.currentState.saveMemento();
210 | this.startingPoints.push(currentMemento);
211 | }
212 |
213 | alterNumberOfKingsAndForetell(numberOfKings: number) {
214 | this.storeMemento();
215 | this.currentState.setState({ numberOfKings });
216 | // run some sort of prediction
217 | }
218 |
219 | alterSeasonAndForetell(season: string) {
220 | this.storeMemento();
221 | this.currentState.setState({ season });
222 | }
223 |
224 | alterCurrentKingInKingsLandingAndForeTell(currentKingInKingsLanding: string) {
225 | this.storeMemento();
226 | this.currentState.setState({ currentKingInKingsLanding });
227 | }
228 |
229 | tryADifferentChange() {
230 | const previousState = this.startingPoints.pop();
231 | this.currentState.restoreMemento(previousState);
232 | }
233 | }
234 | ```
235 |
236 | ## Observer
237 |
238 | - 굉장히 유명한 패턴 중 하나이며, MVVM(Model-View-ViewModel) 구조에서 큰 역할을 맡고 있다.
239 | - 프로티를 Getter와 Setter 인스턴스로 나누어 구분한다고 치고, Setter에서 이벤트가 일어날 때 다른 객체에게 ‘값이 변했다’ 라고 알려준다면?
240 | - 이벤트를 수신하는 객체가 반드시 이전 값을 추적할 필요는 없다.
241 | - 보통은 수신자 하나만 호출하기보다 `subscribe` 라는 개념으로 여러 객체에게 변경 사항을 알릴 수 있다.
242 | - 참고로 책에서 jQuery와 함께 제시된 예시 중 하나인 `document.getElementsByTagName` + `for` 루프는 변수 스코프때문에 원하는대로 동작하지 않는다. -> `buttons` 루프를 돌 때 `forEach` 를 사용해야한다. [JSFiddle](https://jsfiddle.net/gswkm6gv/4/)
243 |
244 | ```typescript
245 | interface Player {
246 | id: number;
247 | name: string;
248 | onKingPainKillerChange(painKillers: number): void;
249 | }
250 |
251 | class Spy {
252 | private painKillers: number;
253 | private partiesToNotify: Player[] = [];
254 |
255 | subscribe(subscriber: Player) {
256 | this.partiesToNotify.push(subscriber);
257 | }
258 |
259 | unsubscribe(subscriber: Player) {
260 | this.partiesToNotify =
261 | this.partiesToNotify.filter(player => player.id !== subscriber.id);
262 |
263 | console.log('Subscribers has changed');
264 | console.log('Current Subscribers: ' +
265 | this.partiesToNotify.map(p => p.name).join(', '));
266 | }
267 |
268 | setPainKillers(painKillers: number) {
269 | this.painKillers = painKillers;
270 |
271 | for (const player of this.partiesToNotify) {
272 | player.onKingPainKillerChange(painKillers);
273 | }
274 | }
275 | }
276 |
277 | function createPlayer(id: number, name: string): Player {
278 | return {
279 | id,
280 | name,
281 | onKingPainKillerChange(painKillers) {
282 | console.log(`Player ${name} - We need ${painKillers} more painKillers!`);
283 | }
284 | }
285 | }
286 |
287 | const spy = new Spy();
288 | const p1 = createPlayer(1, 'John');
289 | const p2 = createPlayer(2, 'Susan');
290 | const p3 = createPlayer(3, 'Pheobe');
291 |
292 | spy.subscribe(p1);
293 | spy.subscribe(p2);
294 | spy.subscribe(p3);
295 |
296 | spy.setPainKillers(12);
297 |
298 | spy.unsubscribe(p2);
299 | ```
300 |
301 | ## State
302 |
303 | - 상태 기계(State machine)은 굉장히 유용한 편이나 각 상태를 구분하는데 다량의 `if` 문이 들어가는 문제가 있다. 이 문제를 걷어내기 위해 거대한 `if` 블록을 만들기 보다 State 패턴을 활용한다.
304 | - State 패턴은 하나의 관리자 클래스가 추상회된 형태로 내부 상태를 관리하고 적절한 상태에 메세지를 전달해주도록 만드는 패턴이다.
305 | - 각각 상태 변화에 일종의 훅을 걸어 조금 다채로운 구현을 끌어낼 수도 있을 것 같고, 각각의 상태 조건이 별도로 관리되고 있다는 점이 중요한 포인트로 보인다.
306 |
307 | ```typescript
308 | interface Actionable {
309 | cancel(): T;
310 | verify(): T;
311 | ship(): T;
312 | }
313 |
314 | interface State extends Actionable {}
315 |
316 | class Order implements Actionable {
317 | private state: State;
318 |
319 | constructor(state: State = new Placed()) {
320 | this.state = state;
321 | }
322 |
323 | cancel(): Order {
324 | return new Order(this.state.cancel());
325 | }
326 |
327 | verify(): Order {
328 | return new Order(this.state.verify());
329 | }
330 |
331 | ship(): Order {
332 | return new Order(this.state.ship());
333 | }
334 | }
335 |
336 | class Placed implements State {
337 | cancel(): State {
338 | console.log("Cancelling the order");
339 | return new Cancelled();
340 | }
341 |
342 | verify(): State {
343 | console.log("Verifying the payment");
344 | return new Verified();
345 | }
346 |
347 | ship(): State {
348 | console.log("Cannot ship. Payment verification is required");
349 | return this;
350 | }
351 | }
352 |
353 | class Cancelled implements State {
354 | cancel(): State {
355 | console.log("Cannot cancel. Order has already been cancelled");
356 | return this;
357 | }
358 |
359 | verify(): State {
360 | console.log("Cannot verify. Order has been cancelled");
361 | return this;
362 | }
363 |
364 | ship(): State {
365 | console.log("Cannot ship. Order has been cancelled");
366 | return this;
367 | }
368 | }
369 |
370 | class Verified implements State {
371 | cancel(): State {
372 | console.log("Cancelling the order");
373 | return new Cancelled();
374 | }
375 |
376 | verify(): State {
377 | console.log("Will not verify. Order has already been verified");
378 | return this;
379 | }
380 |
381 | ship(): State {
382 | console.log("Shipping");
383 | return new Shipped();
384 | }
385 | }
386 |
387 | class Shipped implements State {
388 | cancel(): State {
389 | console.log("Cannot cancel. Order has already been shipped");
390 | return this;
391 | }
392 |
393 | verify(): State {
394 | console.log("Will not verify. Order has already been shipped");
395 | return this;
396 | }
397 |
398 | ship(): State {
399 | console.log("Will not ship. Order has already been shipped");
400 | return this;
401 | }
402 | }
403 |
404 | let order = new Order();
405 | console.log(order);
406 |
407 | order = order
408 | .verify()
409 | .ship()
410 | .cancel();
411 | console.log(order);
412 | ```
413 |
414 | ## Strategy
415 |
416 | - 특정 객체가 필요에 따라 다른 인터페이스를 치환하여 쓸 수 있는 패턴
417 | - 스마트폰이 GPS로 위치를 잡는 것을 예로 들어 표현하고 있는데, 예를 들어 GPS 칩을 직접 이용하여 현재 위치를 잡는 것이 가장 정확도는 높지만 배터리를 많이 소모하고, 주변 Wifi AP를 사용하는 경우 배터리를 적게 먹고 빠르지만 정확도가 매우 떨어지게 된다.
418 | - 알맞은 Strategy(전략)을 선택하는 방법은 몇 가지가 있다.
419 | - 먼저 환경 변수나 하드코딩으로 직접 써넣는 방법이 있다. 전략의 변경이 잦지 않거나 특정한 사용자에 적용되는 전략을 사용할 때 가능하다.
420 | - 제공된 데이터를 기반으로 어느 전략이 적절한지 선택되도록 할 수 있다.
421 | - 특정 알고리즘이 데이터 타입에 따라 실패하는 경우에도 전략을 어떻게 선택할지 고르게 할 수 있다. 웹 애플리케이션의 경우 데이터 유형에 따라 다른 엔드포인트로 요청을 하게 만들 수 있다.
422 | - 빠르고 부정확한 알고리즘과 느리지만 정확한 알고리즘이 동시에 실행되도록 한 뒤에 순차적으로 끼워넣는 방법을 선택할 수도 있다.
423 | - 완전히 랜덤으로 선택하는 방법도 있는데, 서로 다른 전략을 비교하는데 사용될 수 있으며 이 아이디어가 A/B 테스팅의 근간이 된다.
424 | - 어떤 전략을 사용할지 고르는 것은 Factory pattern과 잘 맞아떨어진다.
425 |
426 | ```typescript
427 | interface ITravelMethod {
428 | travel(source: string, destination: string): ITravelResult;
429 | }
430 |
431 | interface ITravelResult {
432 | source: string;
433 | destination: string;
434 | durationInDays: number;
435 | probabilityOfDeath: number;
436 | cost: number;
437 | }
438 |
439 | class SeaGoingVessel implements ITravelMethod {
440 | travel(source, destination) {
441 | return {
442 | source,
443 | destination,
444 | durationInDays: 15,
445 | probabilityOfDeath: 0.25,
446 | cost: 500
447 | };
448 | }
449 | }
450 |
451 | class Horse implements ITravelMethod {
452 | travel(source, destination) {
453 | return {
454 | source,
455 | destination,
456 | durationInDays: 30,
457 | probabilityOfDeath: 0.25,
458 | cost: 50
459 | };
460 | }
461 | }
462 |
463 | class Walk implements ITravelMethod {
464 | travel(source, destination) {
465 | return {
466 | source,
467 | destination,
468 | durationInDays: 150,
469 | probabilityOfDeath: 0.55,
470 | cost: 0
471 | };
472 | }
473 | }
474 |
475 | function getCurrentMoney(): number {
476 | return Math.floor(Math.random() * 100);
477 | }
478 |
479 | const currentMoney = getCurrentMoney();
480 | let strat: ITravelMethod;
481 |
482 | if (currentMoney > 500) {
483 | strat = new SeaGoingVessel();
484 | } else if (currentMoney > 50) {
485 | strat = new Horse();
486 | } else {
487 | strat = new Walk();
488 | }
489 |
490 | console.log(strat.travel('Seoul', 'Jeju'));
491 | ```
492 |
493 | ## Template Method
494 |
495 | - Strategy 패턴처럼 자주 전체 알고리즘을 바꾸는 일은 과한 작업이다. 보통은 대부분의 로직은 변하지 않지만 작은 부분만 변하기 때문이다.
496 | - 템플릿 메서드는 일부분은 공통의 알고리즘을 사용하고 다른 부분은 각기 다른 접근방식으로 구현되도록 하는 패턴이다.
497 | - 이를 적극적으로 활용한 예가 추상 클래스 제작 및 추상클래스의 단계별 상속이다.
498 |
499 | ## Visitor
500 |
501 | - 알고리즘과 그 알고리즘이 작동하는 객체의 분리를 위한 메서드를 만드는 패턴
502 | - 특정 타입에 따라 Visitor가 행위를 수행할지 말지 결정하게 만든다. 다만 자바스크립트에서는 명확하게 타입 체킹을 하기 어렵기 때문에 인스턴스 변수에 `type` 같은 속성을 두어 구분하게 만들 수는 있다.
503 | - 구현할 때 어려운 점이 있는데, 방문자가 방문한 객체에 따라 실행할 메서드를 고르는 방식으로 구현하게 된다면 로직이 복잡해지고, 일관된 인터페이스를 사용하도록 만들면 ‘타겟 객체는 방문자의 존재를 알면 안된다’ 는 개념에 반하게 된다.
504 | - 저자는 Visitor 패턴을 자바스크립트로 구현하는 것은 요구사항이 복잡하고 분명하지 않기 때문에 잘 쓰지 않는 경향이 있다고 한다.
505 |
506 | ```typescript
507 | namespace Visitor {
508 | interface IElementVisitor {
509 | visitElement(element: Element): void;
510 | visitElementNode(elementNode: ElementNode): void;
511 | }
512 |
513 | export class Element {
514 | private _name: string;
515 | private _parent: ElementNode;
516 |
517 | constructor(name: string, parent?: ElementNode) {
518 | if (!name) {
519 | throw new Error("Argument null exception!");
520 | }
521 |
522 | this._name = name;
523 | this._parent = parent;
524 | }
525 |
526 | get name(): string {
527 | return this._name;
528 | }
529 |
530 | get parent(): ElementNode {
531 | return this._parent;
532 | }
533 |
534 | set parent(value: ElementNode) {
535 | this._parent = value;
536 | }
537 |
538 | get depth(): number {
539 | if (this._parent) {
540 | return this._parent.depth + 1;
541 | }
542 |
543 | return 0;
544 | }
545 |
546 | accept(visitor: IElementVisitor) {
547 | visitor.visitElement(this);
548 | }
549 | }
550 |
551 | export class ElementNode extends Element {
552 | private _children: Element[] = [];
553 |
554 | constructor(name: string, parent?: ElementNode) {
555 | super(name, parent);
556 | }
557 |
558 | get length(): number {
559 | return this._children.length;
560 | }
561 |
562 | appendChild(child: Element): ElementNode {
563 | child.parent = this;
564 | this._children.push(child);
565 | return this;
566 | }
567 |
568 | accept(visitor: IElementVisitor) {
569 | visitor.visitElementNode(this);
570 | this._children.forEach(function(child) {
571 | child.accept(visitor);
572 | });
573 | }
574 | }
575 |
576 | export class LogWriter implements IElementVisitor {
577 | visitElement(element: Element) {
578 | console.log("LogWriter is visiting the element: '" + element.name + "'");
579 | }
580 |
581 | visitElementNode(elementNode: ElementNode) {
582 | console.log(
583 | "LogWrite is visiting the element node: '" +
584 | elementNode.name +
585 | "'. Which has: " +
586 | elementNode.length +
587 | " child nodes."
588 | );
589 | }
590 | }
591 |
592 | export class ConsoleWriter implements IElementVisitor {
593 | visitElement(element: Element) {
594 | console.log(
595 | "ConsoleWriter is visiting the element: '" + element.name + "'"
596 | );
597 | }
598 |
599 | visitElementNode(elementNode: ElementNode) {
600 | console.log(
601 | "ConsoleWriter is visiting the element node: '" +
602 | elementNode.name +
603 | "'. Which has: " +
604 | elementNode.length +
605 | " child nodes."
606 | );
607 | }
608 | }
609 | }
610 |
611 | const constructedTree = new Visitor.ElementNode("first")
612 | .appendChild(new Visitor.Element("firstChild"))
613 | .appendChild(
614 | new Visitor.ElementNode("secondChild").appendChild(
615 | new Visitor.Element("furtherDown")
616 | )
617 | );
618 | const logwriter = new Visitor.LogWriter();
619 | constructedTree.accept(logwriter);
620 |
621 | const consolewriter = new Visitor.ConsoleWriter();
622 | constructedTree.accept(consolewriter);
623 | ```
624 |
--------------------------------------------------------------------------------
/05_행동_패턴/HWANGWONJUN.md:
--------------------------------------------------------------------------------
1 | ```js
2 | class Complaint {
3 | constructor() {
4 | this.ComplainingParty = ''
5 | this.ComplaintAbout = ''
6 | this.Complaint = ''
7 | }
8 | }
9 |
10 | class ClerkOfTheCourt {
11 | isAbleToResolveComplaint(complaint) {
12 | // 서기가 해결할수 있는가 ?
13 | return true
14 | }
15 |
16 | listenToComplaint(complaint) {
17 | return true
18 | }
19 | }
20 |
21 | class King {
22 | isAbleToResolveComplaint(complaint) {
23 | // 왕이 해결할수 있는가?
24 | return true
25 | }
26 |
27 | listenToComplaint(complaint) {
28 | return true
29 | }
30 | }
31 |
32 |
33 |
34 | class ComplaintResolver {
35 | constructor() {
36 | this.complaintListeners = []
37 | this.complaintListeners.push (new ClerkOfTheCourt())
38 | this.complaintListeners.push (new King())
39 | }
40 |
41 | resolveComplaint(complaint) {
42 | for (var i = 0; i < this.complaintListeners.length; i++) {
43 | if (this.complaintListeners[i].isAbleToResolveComplaint(complaint))
44 | this.complaintListeners[i].listenToComplaint(complaint)
45 | }
46 | }
47 | }
48 |
49 |
50 | ```
51 |
--------------------------------------------------------------------------------
/05_행동_패턴/JINHO.md:
--------------------------------------------------------------------------------
1 | # 5. 행동 패턴 - 현진호
2 |
3 | - 객체 간 상호작용을 용이하게 구성해줌
4 | ### 책임연쇄 Chain of Responsibility
5 | ### 명령 Command
6 | ### 해석자 Interpreter
7 | ### 반복자 Iterator
8 | ### 중재자 Mediator
9 | ### 메멘토 Memento
10 | ### 감시자 Observer
11 | ### 상태 State
12 | ### 전략 Strategy
13 | ### 템플릿 메소드 Template method
14 | ### 방문자 Visitor
15 |
16 | ### 책임연쇄 Chain of Responsibility
17 | - 메시지가 클래스에서 다른 클래스로 전달되는 접근 방식을 기술
18 | - 메시지를 처리허가나
19 | - 체인에 있는 다음 멤버에게 전달
20 | - 브라우저 기반 자바스크립트에서, 이벤트는 책임의 연쇄를 통해 전달
21 | - 링크에 복수의 클릭 이벤트 리스너 연결 시, 각각은 마지막으로 기본 탐색 리스너가 발생할 때까지 계속
22 |
23 | ```js
24 | class Complaint {
25 | constructor() {
26 | this.ComplainingParty = '';
27 | this.ComplainAbout = '';
28 | this.Complaint = '';
29 | }
30 | }
31 |
32 | class ClekOfTheCourt {
33 | IsAbleToResolveComplaint(complaint) {
34 | return false;
35 | }
36 |
37 | ListenToComplaint(complaint) {
38 | // 몇 가지 작업을 수행
39 | // 불만에 대한 해결책을 반환
40 | return '';
41 | }
42 | }
43 |
44 | class King {
45 | IsAbleToResolveComplaint(complaint) {
46 | return true;
47 | }
48 |
49 | ListenToComplaint(complaint) {
50 | // 몇 가지 작업을 수행
51 | // 불만에 대한 해결책을 반환
52 | return '';
53 | }
54 | }
55 |
56 | class ComplaintResolver {
57 | constructor() {
58 | complaintListeners = new Array();
59 | complaintListeners.push(new ClekOfTheCourt());
60 | complaintListeners.push(new King());
61 | }
62 |
63 | ResolveComplaint(complaint) {
64 | for (var i = 0; i < this.complaintListeners.length; i++) {
65 | if (this.complaintListeners[i].IsAbleToResolveComplaint(complaint)) {
66 | return this.complaintListeners[i].ListenToComplaint(complaint);
67 | }
68 | }
69 | }
70 | }
71 | ```
72 |
73 | ### 명령 Command
74 | - 메소드의 매개변수, 객체의 현재 상태 모두를 캡슐화하고 메소드를 호출
75 | - 메소드를 나중에 호출할 때 필요한 것들을 패키지로 포장
76 | - 먼저 명령을 실행하고, 어떤 코드가 명령을 실행할 지 나중에 결정할 때까지 대기
77 |
78 | - 구성 요소
79 | - 명령 메시지: 명령 그 자체
80 | - 함수를 객체로 저장. 함수와 매개변수들을 추적.
81 | - 명령이 실행 시에 실패하지 않고 생성 시에 실패하도록 보장하여 디버깅 용이
82 | ```js
83 | class BringTroopsCommand {
84 | constructor(location, numberOfTroops, when) {
85 | this._location = location;
86 | this._numberOfTroops = numberOfTroops;
87 | this._when = when;
88 | }
89 |
90 | Execute() {
91 | var receiver = new LordInstructions();
92 | receiver.BringTroops(this._location, this._numberOfTroops, this._when);
93 | }
94 | }
95 | ```
96 |
97 | - 호출자: 명령의 실행을 지시
98 | ```js
99 | var bringTroopsCommand = new BringTroopsCommand("King's Landing", 500, new Date());
100 | bringTroopsCommand.Execute();
101 |
102 | ```
103 |
104 | - 수신자: 명령 실행의 대상
105 | - 지연된 명령을 어떻게 수행할 지 정의
106 | ```js
107 | class LordInstructions {
108 | constructor() {}
109 | BringTroops(location, numberOfTroops, when) {
110 | console.log(`Bring ${numberOfTroops} troops to ${location} at ${when}`)
111 | }
112 | }
113 | ```
114 |
115 | ### 해석자 Interpreter
116 | - 자신만의 고유한 언어를 생성할 수 있게 하는 패턴
117 | - 예: (agressor -> battle ground <- defender) -> victor
118 |
119 | ```js
120 | class Battle {
121 | constructor(battleGround, agressor, defender, victor) {
122 | this.battleGround = battleGround;
123 | this.agressor = agressor;
124 | this.defender = defender;
125 | this.victor = victor;
126 | }
127 | }
128 |
129 | class Parser {
130 | constructor(battleText) {
131 | this.battleText = battleText;
132 | this.currentIndex = 0;
133 | this.battleList = battleText.split('\n')
134 | }
135 |
136 | nextBattle() {
137 | if (!this.battleList[0]) {
138 | return null;
139 | }
140 | var segments = this.battleList[0].match(/\((.+?)\s?->\s?(.+?)\s?<-\s?(.+?)\)\s?->\s?(.+)/);
141 | return new Battle(segments[2], segments[1], segments[3], segments[4]);
142 | }
143 | }
144 |
145 | var text = "(Robert Baratheon -> River Trident <- RhaegarTargaryen) -> Robert Baratheon";
146 | var p = new Parser(text);
147 | p.nextBattle();
148 | /*
149 | Battle {
150 | battleGround: "River Trident",
151 | agressor: "Robert Baratheon",
152 | defender: "RhaegarTargaryen",
153 | victor: "Robert Baratheon"
154 | }
155 | */
156 | ```
157 |
158 | ### 반복자 Iterator
159 | - 객체들의 집합 내 이동을 위한 특별한 생성자
160 | - 집합에서 다음 항목을 순차적으로 선택하는 간단한 메소드를 제공해주는 패턴
161 |
162 | ```js
163 | class KingSuccession {
164 | constructor(inLineForThrone) {
165 | this.inLineForThrone = inLineForThrone;
166 | this.pointer = 0;
167 | }
168 |
169 | next() {
170 | return this.inLineForThrone[this.pointer++];
171 | }
172 | }
173 |
174 | var king = new KingSuccession(['Robert Baratheon', 'Joffery Baratheon', 'Tommen Baratheon']);
175 | console.log(king.next()); // Robert Baratheon
176 | console.log(king.next()); // Joffery Baratheon
177 | console.log(king.next()); // Tommen Baratheon
178 | ```
179 |
180 | - 고정돼 있지 않은 집합을 반복하는 경우도 있음
181 | ```js
182 | class FibonacciIterator {
183 | constructor() {
184 | this.previous = 1;
185 | this.beforePrevious = 1;
186 | }
187 |
188 | next() {
189 | var current = this.previous + this.beforePrevious;
190 | this.beforePrevious = this.previous;
191 | this.previous = current;
192 | return current;
193 | }
194 | }
195 |
196 | var fib = new FibonacciIterator();
197 | console.log(fib.next()); // 2
198 | console.log(fib.next()); // 3
199 | console.log(fib.next()); // 5
200 | console.log(fib.next()); // 8
201 | console.log(fib.next()); // 13
202 | console.log(fib.next()); // 21
203 | ```
204 |
205 | ### 중재자 Mediator
206 | - 다양한 컴포넌트의 중간에 위치, 메시지의 경로 변경이 이루어지는 유일한 장소로 기능
207 | - 코드 유지히 필요한 복잡한 작업을 단순화
208 | - 참여자들 사이의 정보 교환을 명확히 하고 중계함
209 |
210 | ```js
211 | class Karstark {
212 | constructor(greatLord) {
213 | this.greatLord = greatLord;
214 | }
215 |
216 | receiveMessage(message) {}
217 |
218 | sendMessage(message) {
219 | this.greatLord.routeMessage(message);
220 | }
221 | }
222 |
223 | class HouseStark {
224 | constructor() {
225 | this.karstark = new Karstark(this);
226 | this.bolton = new Bolton(this);
227 | this.frey = new Frey(this);
228 | this.umber = new Umber(this);
229 | }
230 |
231 | routeMessage(message) {}
232 | }
233 | ```
234 |
235 | ### 메멘토 Memento
236 | - 이전 상태로 객체의 상태를 복원할 수 있는 방법을 제공
237 | - 변수의 이전 값에 대한 기록 유지, 복원 기능 제공
238 |
239 | - 메멘토 구성을 위해서 필요한 구성원
240 | - 발신자: 상태정보 유지, 새로운 메멘토 생성 인터페이스 제공
241 | - 관리자: 패턴의 클라이언트. 새로운 메멘토 요청, 복원 시 이를 관리
242 | - 메멘토: 발신자의 저장된 상태의 표현. 롤백을 위해 저장해야 하는 값
243 |
244 | ```js
245 | class WorldState {
246 | constructor(numberOfKings, currentKingInKingsLanding, season) {
247 | this.numberOfKings = numberOfKings;
248 | this.currentKingInKingsLanding = currentKingInKingsLanding;
249 | this.season = season;
250 | }
251 | }
252 |
253 | class WorldStateProvider {
254 | constructor() {
255 |
256 | }
257 |
258 | saveMemento() {
259 | return new WorldState(this.numberOfKings, this.currentKingInKingsLanding, this.season);
260 | }
261 |
262 | restoreMemento(memento) {
263 | this.numberOfKings = memento.numberOfKings;
264 | this.currentKingInKingsLanding = memento.currentKingInKingsLanding;
265 | this.season = memento.season;
266 | }
267 | }
268 |
269 | class Soothsayer {
270 | constructor() {
271 | this.startingPoints = [];
272 | this.currentState = new WorldStateProvider();
273 | }
274 |
275 | setInitialConditions(numberOfKings, currentKingInKingsLanding, season) {
276 | this.currentState.numberOfKings = numberOfKings;
277 | this.currentState.currentKingInKingsLanding = currentKingInKingsLanding;
278 | this.currentState.season = season;
279 | }
280 |
281 | alterNumberOfKingsAndForetell(numberOfKings) {
282 | this.startingPoints.push(this.currentState.saveMemento());
283 | this.currentState.numberOfKings = numberOfKings;
284 | }
285 |
286 | alertSeasonAndForetell(season) {
287 | this.startingPoints.push(this.currentState.saveMemento());
288 | this.currentState.season = season;
289 | }
290 |
291 | alertCurrentKingInKingsLandingAndForetell(currentKingInKingsLanding) {
292 | this.startingPoints.push(this.currentState.saveMemento());
293 | this.currentState.currentKingInKingsLanding = currentKingInKingsLanding;
294 | }
295 |
296 | tryADifferentChange() {
297 | this.currentState.restoreMemento(this.startingPoints.pop());
298 | }
299 | }
300 |
301 | var soothsayer = new Soothsayer();
302 | soothsayer.setInitialConditions(3, 'Jinho', 'Spring');
303 | soothsayer.alterNumberOfKingsAndForetell(5);
304 | soothsayer.tryADifferentChange();
305 | ```
306 |
307 | ### 감시자 Observer
308 | - 객체의 값 변화를 알고 싶을 때 사용
309 | - 이를 위해 getter, setter를 사용해 원하는 속성 래핑
310 |
311 | ```js
312 | class Spy {
313 | constructor() {
314 | this._partiesToNotify = [];
315 | }
316 |
317 | Subscribe(subscriber) {
318 | this._partiesToNotify.push(subscriber);
319 | }
320 |
321 | Unsubscribe(subscriber) {
322 | this._partiesToNotify.remove(subscriber);
323 | }
324 |
325 | SetPainKillers(painKillers) {
326 | for (var i = 0; i < this._partiesToNotify.length; i++) {
327 | this._partiesToNotify[i](painKillers);
328 | }
329 | }
330 | }
331 |
332 | class Player {
333 | constructor() {
334 |
335 | }
336 |
337 | OnKingPainKillerChange(newPainKillerAmount) {
338 | console.log(newPainKillerAmount);
339 | }
340 | }
341 |
342 | var s = new Spy();
343 | var p = new Player();
344 |
345 | s.Subscribe(p.OnKingPainKillerChange);
346 | s.SetPainKillers(12);
347 | ```
348 |
349 | ### 상태 State
350 | - 복잡하고 거대한 if 문 블록 대신, 내부 상태를 추상화.
351 | - 클래스로 구현된 상태들 사이의 메시지를 프록시로 전달하는 상태 관리자를 가짐
352 | - 상태 관리자의 인터페이스는 간단, 개별 상태와 통신하는 데 필요한 메소드만 제공
353 | - 코드 디버깅 용이, 작은 코드 블록 형성으로 테스트 용이
354 |
355 | ```js
356 | class BankAccountManager {
357 | constructor() {
358 | this.currentState = new GoodStandingState(this);
359 | this.balance = 0;
360 | }
361 |
362 | Deposit(amount) {
363 | this.currentState.Deposit(amount);
364 | }
365 |
366 | Withdraw(amount) {
367 | this.currentState.Withdraw(amount);
368 | }
369 |
370 | addToBalance(amount) {
371 | this.balance += amount;
372 | }
373 |
374 | getBalance() {
375 | return this.balance;
376 | }
377 |
378 | moveToState(newState) {
379 | this.currentState = newState;
380 | }
381 | }
382 |
383 | class GoodStandingState {
384 | constructor(manager) {
385 | this.manager = manager;
386 | }
387 |
388 | Deposit(amount) {
389 | this.manager.addToBalance(amount);
390 | }
391 |
392 | Withdraw(amount) {
393 | if (this.manager.getBalance() < amount) {
394 | this.manager.moveToState(new OverdrawnState(this.manager));
395 | }
396 |
397 | this.manager.addToBalance(-1 * amount);
398 | }
399 | }
400 |
401 | class OverdrawnState {
402 | constructor(manager) {
403 | this.manager = manager;
404 | }
405 |
406 | Deposit(amount) {
407 | this.manager.addToBalance(amount);
408 | if (this.manager.getBalance() > 0) {
409 | this.manager.moveToState(new GoodStandingState(this.manager));
410 | }
411 | }
412 |
413 | Withdraw(amount) {
414 | this.manager.moveToState(new Onhold(this.manager));
415 | throw 'Cannot withdraw money from an already overdrawn bank account';
416 | }
417 | }
418 |
419 | class Onhold {
420 | constructor(manager) {
421 | this.manager = manager;
422 | }
423 |
424 | Deposit(amount) {
425 | this.manager.addToBalance(amount);
426 | throw 'Your account is on hold and you must go to the bank to resolve the issue';
427 | }
428 |
429 | Withdraw(amount) {
430 | throw 'Your account is on hold and you must go to the bank to resolve the issue';
431 | }
432 | }
433 |
434 | var bankAccountManager = new BankAccountManager();
435 | bankAccountManager.Deposit(10000);
436 |
437 | bankAccountManager.Withdraw(12000);
438 |
439 | bankAccountManager.Deposit(1999);
440 | bankAccountManager.Withdraw(1); // Cannot withdraw money from an already overdrawn bank account
441 | ```
442 |
443 |
444 | ### 전략 Strategy
445 | - 전략 패턴을 전략들을 투명하게 교체하는 방법을 제공. 알고리즘 전체를 다른 알고리즘으로 대체
446 | - 올바른 전략 선택에는 여러 다양한 방법이 있다
447 | - 정적으로 전략을 선택 : 전략이 자주 변경되지 않거나, 단일 고객을 위한 특정 전략일 경우
448 | - 데이터 세트 분석 후 가장 적합한 전략 선택
449 | - 점진적 향상
450 | - 사용자들에게 신속한 피드백 제공을 위해 가장 빠르지만 부정확한 알고리즘 실행
451 | - 동시에 가장 느린 알고리즘 실행. 완료 후 더 정확한 결과로 기존의 결과 대체
452 | - 무작위 선택 : 서로 다른 두 개의 전략의 성능을 비교하는 경우
453 |
454 | ```js
455 | class TravelResult {
456 | constructor(durationInDays, probabilityOfDeath, cost) {
457 | this.durationInDays = durationInDays;
458 | this.probabilityOfDeath = probabilityOfDeath;
459 | this.cost = cost;
460 | }
461 | }
462 |
463 | class SeaGoingVessel {
464 | Travel(source, destination) {
465 | return new TravelResult(15, .25, 500);
466 | }
467 | }
468 |
469 | class Horse {
470 | Travel(source, destination) {
471 | return new TravelResult(30, .25, 50);
472 | }
473 | }
474 |
475 | class Walk {
476 | Travel(source, destination) {
477 | return new TravelResult(150, .55, 0);
478 | }
479 | }
480 |
481 | var currentMoney = getCurrentMoney();
482 | var strat;
483 | if (currentMoney > 500) {
484 | start = new SeaGoingVessel();
485 | } else if (currentMoney > 50) {
486 | strat = new Horse();
487 | } else {
488 | strat = new Walk;
489 | }
490 |
491 | var travelResult = strat.Travel();
492 | console.log(travelResult);
493 | ```
494 |
495 | ### 템플릿 메소드 Template method
496 | - 모든 전략에서 알고리즘의 대부분이 동일하고 아주 조금만 다를 경우, 전체 알고리즘을 대체하는 것은 부담
497 | - 템플릿 메서드는 알고리즘의 일부분은 공유, 일부분만 다른 방법으로 구현
498 | - 점진적 향상의 원칙을 적용하여, 점점 더 완벽한 알고리즘을 구현하며 동시에 상속 트리 구조를 구축
499 |
500 | ```js
501 | class BasicBeer {
502 | Create() {
503 | this.AddIngredients();
504 | this.Stir();
505 | this.Ferment();
506 | this.Test();
507 | if (this.TestingPassed()) {
508 | this.Distribute();
509 | }
510 | }
511 |
512 | AddIngredients() {}
513 | Stir() {}
514 | Ferment() {}
515 | Test() {}
516 | TestingPassed() {}
517 | Distribute() {}
518 | }
519 |
520 | class RaspberryBeer extends BasicBeer {
521 | AddIngredients() {
522 | console.log('Add Ingredients');
523 | }
524 | TestingPassed() {
525 | console.log('Testing Passed');
526 | }
527 | }
528 |
529 | const raspberryBeer = new RaspberryBeer();
530 | raspberryBeer.Create();
531 |
532 | ```
533 |
534 | ### 방문자 Visitor
535 | - 알고리즘을 객체의 구조와 분리
536 | - 유형이 다른 객체의 집합들 사이에서 유형에 따라 다른 동작을 수행
537 |
538 | ```js
539 | class Knight {
540 | printName() { console.log('Knight'); }
541 | }
542 |
543 | class FootSoldier {
544 | printName() { console.log('FootSoldier'); }
545 | }
546 |
547 | class Lord {
548 | printName() { console.log('Lord'); }
549 | }
550 |
551 | class Archer {
552 | printName() { console.log('Archer'); }
553 | }
554 |
555 | var collection = [];
556 | collection.push(new Knight());
557 | collection.push(new FootSoldier());
558 | collection.push(new Lord());
559 | collection.push(new Archer());
560 |
561 | for (const soldier of collection) {
562 | if (typeof (soldier) === 'Knight') {
563 | soldier.printName();
564 | } else {
565 | console.log('Not a knight')
566 | }
567 | }
568 |
569 | /*
570 | Not a knight
571 | Not a knight
572 | Not a knight
573 | Not a knight
574 | */
575 |
576 | console.log('----');
577 | for (const soldier of collection) {
578 | if (soldier instanceof Knight) {
579 | soldier.printName();
580 | } else {
581 | console.log('Not a knight')
582 | }
583 | }
584 |
585 | /*
586 | Knight
587 | Not a knight
588 | Not a knight
589 | Not a knight
590 | */
591 |
592 | class Knight2 {
593 | constructor() {
594 | this._type = 'Knight';
595 | }
596 |
597 | printName() {
598 | console.log('Knight');
599 | }
600 | }
601 |
602 |
603 | var collection = [];
604 | collection.push(new Knight2());
605 | collection.push(new FootSoldier());
606 | collection.push(new Lord());
607 | collection.push(new Archer());
608 |
609 | console.log('----');
610 | for (const soldier of collection) {
611 | if (soldier._type === 'Knight') {
612 | soldier.printName();
613 | } else {
614 | console.log('Not a knight')
615 | }
616 | }
617 |
618 | /*
619 | Knight
620 | Not a knight
621 | Not a knight
622 | Not a knight
623 | */
624 |
625 | Knight2.prototype.visit = function (visitor) {
626 | visitor.Visit(this);
627 | }
628 |
629 | FootSoldier.prototype.visit = function (visitor) {
630 | visitor.Visit(this);
631 | }
632 |
633 | Lord.prototype.visit = function (visitor) {
634 | visitor.Visit(this);
635 | }
636 |
637 | Archer.prototype.visit = function (visitor) {
638 | visitor.Visit(this);
639 | }
640 |
641 | class SelectiveNamePrinterVisitor {
642 | Visit(memberOfArmy) {
643 | if (memberOfArmy._type === 'Knight') {
644 | this.VisitKnight(memberOfArmy);
645 | } else {
646 | console.log('Not a knight');
647 | }
648 | }
649 |
650 | VisitKnight(memberOfArmy) {
651 | memberOfArmy.printName();
652 | }
653 | }
654 |
655 | console.log('----');
656 | var visitor = new SelectiveNamePrinterVisitor();
657 | for (const soldier of collection) {
658 | soldier.visit(visitor);
659 | }
660 |
661 | ```
662 |
--------------------------------------------------------------------------------
/05_행동_패턴/Jieun.md:
--------------------------------------------------------------------------------
1 | ## Chain of Responsibility
2 | 객체 지향 디자인에서 chain-of-responsibility pattern은 명령 객체와 일련의 처리 객체를 포함하는 디자인 패턴이다.
3 | 각각의 처리 객체는 명령 객체를 처리할 수 있는 연산의 집합이고, 체인 안의 처리 객체가 핸들할 수 없는 명령은 다음 처리 객체로 넘겨진다. 이 작동방식은 새로운 처리 객체부터 체인의 끝까지 다시 반복된다.
4 | ```javascript
5 | var Complaint = (function () {
6 | function Complaint() {
7 | this.ComplainingParty = "";
8 | this.ComplaintAbout = "";
9 | this.Complaint = "";
10 | }
11 | return Complaint;
12 | })();
13 |
14 | var ClerkOfTheCourt = (function () {
15 | function ClerkOfTheCourt() {
16 | }
17 | ClerkOfTheCourt.prototype.IsAbleToResolveComplaint = function (complaint) {
18 | //decide if this is a complaint which can be solved by the clerk
19 | return false;
20 | };
21 |
22 | ClerkOfTheCourt.prototype.ListenToComplaint = function (complaint) {
23 | //perform some operation
24 | //return solution to the complaint
25 | return "clerk resolved ";
26 | };
27 | return ClerkOfTheCourt;
28 | })();
29 |
30 | var King = (function () {
31 | function King() {
32 | }
33 | King.prototype.IsAbleToResolveComplaint = function (complaint) {
34 | return true;
35 | };
36 |
37 | King.prototype.ListenToComplaint = function (complaint) {
38 | //perform some operation
39 | //return solution to the complaint
40 | return "King resolved";
41 | };
42 | return King;
43 | })();
44 |
45 | var ComplaintResolver = (function () {
46 | function ComplaintResolver() {
47 | this.complaintListeners = new Array();
48 | this.complaintListeners.push(new ClerkOfTheCourt());
49 | this.complaintListeners.push(new King());
50 | }
51 | ComplaintResolver.prototype.ResolveComplaint = function (complaint) {
52 | for (var i = 0; i < this.complaintListeners.length; i++) {
53 | if (this.complaintListeners[i].IsAbleToResolveComplaint(complaint)) {
54 | return this.complaintListeners[i].ListenToComplaint(complaint);
55 | }
56 | }
57 | };
58 | return ComplaintResolver;
59 | })();
60 |
61 | var resolver = new ComplaintResolver();
62 | resolver.ResolveComplaint(new Complaint()); // King resolved
63 | ```
64 |
65 | ## Command Pattern
66 | Command 패턴은 메소드의 매개변수와 객체의 현재 상태 모두를 캡슐화하고, 메소드를 호출하는 방법이다.(메소드 호출 캡슐화)
67 | 메소드를 나중에 호출할 때 필요한 것들을 작은 패키지로 포장한다.
68 |
69 | ```javascript
70 | var Vitellius = (function() {
71 | function Vitellius() {}
72 | Vitellius.prototype.approve = function(commander) {
73 | commander.execute();
74 | };
75 | return Vitellius;
76 | })();
77 |
78 | var Commander = (function() {
79 | function Commander() {
80 | this.commands = [];
81 | }
82 | Commander.prototype.execute = function() {
83 | this.commands.forEach(function(command) {
84 | command();
85 | });
86 | };
87 | Commander.prototype.do = function(command, args) {
88 | this.commands.push(function() {
89 | command.call(null, args);
90 | });
91 | };
92 | Commander.prototype.undo = function() {
93 | this.commands.pop();
94 | };
95 | return Commander;
96 | })();
97 |
98 | var strategy = {
99 | climbAlps: function() {
100 | console.log('알프스를 오릅니다');
101 | },
102 | prepareSupply: function(number) {
103 | console.log('보급품을 ' + number + '만큼 준비합니다');
104 | },
105 | attackRome: function() {
106 | console.log('로마를 공격합니다');
107 | },
108 | };
109 |
110 | var vitellius = new Vitellius();
111 | var caecina = new Commander();
112 | caecina.do(strategy.prepareSupply, 5000);
113 | caecina.undo(); // prepareSupply 취소
114 | caecina.do(strategy.prepareSupply, 10000);
115 | caecina.do(strategy.climbAlps);
116 | caecina.do(strategy.attackRome);
117 | vitellius.approve(caecina); // 보급품을 10000만큼 준비합니다. 알프스를 오릅니다. 로마를 공격합니다.
118 | ```
119 |
120 | ## Interpreter Pattern
121 | 해석자(Interpreter) 패턴은 자신만의 고유한 언어를 생성할 수 있게 해주는 패턴이다.
122 | ```javascript
123 | var Battle = (function () {
124 | function Battle(battleGround, agressor, defender, victor) {
125 | this.battleGround = battleGround;
126 | this.agressor = agressor;
127 | this.defender = defender;
128 | this.victor = victor;
129 | }
130 | return Battle;
131 | })();
132 |
133 | var Parser = (function () {
134 | function Parser(battleText) {
135 | this.battleText = battleText;
136 | this.currentIndex = 0;
137 | this.battleList = battleText.split("\n");
138 | }
139 | Parser.prototype.nextBattle = function () {
140 | if (!this.battleList[0])
141 | return null;
142 | var segments = this.battleList[0].match(/\((.+?)\s?->\s?(.+?)\s?<-\s?(.+?)\s?->\s?(.+)/);
143 | return new Battle(segments[2], segments[1], segments[3], segments[4]);
144 | };
145 | return Parser;
146 | })();
147 |
148 | var parser = new Parser('aa->bb->at');
149 | console.dir(parser.nextBattle())
150 | ```
151 | ## Iterator Pattern
152 | 반복자 패턴은 객체 들의 집합 내 이동은 매우 일반적인 문제여서 많은 언어들이 이 집합들 내에서 이동하는 특별한 생성자를 제공한다.
153 | ```javascript
154 | var KingSuccession = (function () {
155 | function KingSuccession(inLineForThrone) {
156 | this.inLineForThrone = inLineForThrone;
157 | this.pointer = 0;
158 | }
159 | KingSuccession.prototype.next = function () {
160 | return this.inLineForThrone[this.pointer++];
161 | };
162 | return KingSuccession;
163 | })();
164 |
165 | var FibonacciIterator = (function () {
166 | function FibonacciIterator() {
167 | this.previous = 1;
168 | this.beforePrevious = 1;
169 | }
170 | FibonacciIterator.prototype.next = function () {
171 | var current = this.previous + this.beforePrevious;
172 | this.beforePrevious = this.previous;
173 | this.previous = current;
174 | return current;
175 | };
176 | return FibonacciIterator;
177 | })();
178 |
179 | var fibo = new FibonacciIterator();
180 | console.log(fibo.next())//2
181 | console.log(fibo.next())//3
182 | console.log(fibo.next())//5
183 | // ...
184 |
185 | ```
186 |
187 | ## Mediator Pattern(중재자 패턴)
188 | 여러개의 객체들을 관리하는 패턴
189 | 다양한 컴포넌트의 중간에 위치하여 메세지의 경로 변경이 이루어지는 유일한 장소로써의 동작
190 |
191 | ```javascript
192 | var BroadCast = (function() {
193 | function BroadCast() {
194 | this.participants = [];
195 | }
196 | BroadCast.prototype.register = function(participant) {
197 | this.participants.push(participant);
198 | };
199 | BroadCast.prototype.deliver = function(sender, message) {
200 | this.participants.forEach(function(participant) {
201 | if (participant !== sender) {
202 | console.log(sender + '님이 ' + participant + '님에게 "' + message + '"라고 말합니다.');
203 | }
204 | });
205 | };
206 | return BroadCast;
207 | })();
208 | var broadcast = new BroadCast();
209 | broadcast.register('MBC');
210 | broadcast.register('JTBC');
211 | broadcast.register('SBS');
212 | broadcast.register('KBS');
213 | broadcast.deliver('JTBC', ' 방송 방송');
214 | ```
215 |
216 | ## 메멘토 패턴
217 | 이전 상태로 객체의 상태를 복원할 수 있는 방법을 제공한다.
218 | 메멘토는 변수의 이전값에 대한 기록을 유지하고 복원하는 기능을 제공한다.
219 | 명령에 대한 메멘토를 유지하면 복구가 힘든 명령을 쉽게 복원할 수 있다.
220 |
221 | ```javascript
222 | var SquareCommand = (function () {
223 | function SquareCommand(numberToSquare) {
224 | this.numberToSquare = numberToSquare;
225 | }
226 | SquareCommand.prototype.Execute = function () {
227 | this.numberToSquare *= this.numberToSquare;
228 | };
229 | return SquareCommand;
230 | })();
231 |
232 | var WorldState = (function () {
233 | function WorldState(numberOfKings, currentKingInKingsLanding, season) {
234 | this.numberOfKings = numberOfKings;
235 | this.currentKingInKingsLanding = currentKingInKingsLanding;
236 | this.season = season;
237 | }
238 | return WorldState;
239 | })();
240 |
241 | var Soothsayer = (function () {
242 | function Soothsayer() {
243 | this.startingPoints = [];
244 | this.currentState = new WorldStateProvider();
245 | }
246 | Soothsayer.prototype.setInitialConditions = function (numberOfKings, currentKingInKingsLanding, season) {
247 | this.currentState.numberOfKings = numberOfKings;
248 | this.currentState.currentKingInKingsLanding = currentKingInKingsLanding;
249 | this.currentState.season = season;
250 | };
251 | Soothsayer.prototype.alterNumberOfKingsAndForetell = function (numberOfKings) {
252 | this.startingPoints.push(this.currentState.saveMemento());
253 | this.currentState.numberOfKings = numberOfKings;
254 | };
255 | Soothsayer.prototype.alterSeasonAndForetell = function (season) {
256 | this.startingPoints.push(this.currentState.saveMemento());
257 | this.currentState.season = season;
258 | };
259 | Soothsayer.prototype.alterCurrentKingInKingsLandingAndForetell = function (currentKingInKingsLanding) {
260 | this.startingPoints.push(this.currentState.saveMemento());
261 | this.currentState.currentKingInKingsLanding = currentKingInKingsLanding;
262 | };
263 | Soothsayer.prototype.tryADifferentChange = function () {
264 | this.currentState.restoreMemento(this.startingPoints.pop());
265 | };
266 | return Soothsayer;
267 | })();
268 |
269 | var WorldStateProvider = (function () {
270 | function WorldStateProvider() {
271 | }
272 | WorldStateProvider.prototype.saveMemento = function () {
273 | return new WorldState(this.numberOfKings, this.currentKingInKingsLanding, this.season);
274 | };
275 | WorldStateProvider.prototype.restoreMemento = function (memento) {
276 | this.numberOfKings = memento.numberOfKings;
277 | this.currentKingInKingsLanding = memento.currentKingInKingsLanding;
278 | this.season = memento.season;
279 | };
280 | return WorldStateProvider;
281 | })();
282 |
283 |
284 | ```
285 | ## 옵저버 패턴
286 | 객체의 값이 변할 때 그 변화를 알고 싶을 때 유용하게 사용한다.
287 | 브라우저에서 DOM의 모든 다양한 이벤트 리스너는 감시자 패턴으로 구현된다.
288 | 예를 들어 제이쿼리 라이브러리를 사용할 때, 다음 라인과 같이 페이지에 있는 모든 버튼의 클릭 에빈트를 구독할 수 있다.
289 |
290 | ```javascript
291 | var GetterSetter = (function () {
292 | function GetterSetter() {
293 | }
294 | GetterSetter.prototype.GetProperty = function () {
295 | return this._property;
296 | };
297 | GetterSetter.prototype.SetProperty = function (value) {
298 | var temp = this._property;
299 | this._property = value;
300 | this._listener.Event(value, temp);
301 | };
302 | return GetterSetter;
303 | })();
304 |
305 | var Listener = (function () {
306 | function Listener() {
307 | }
308 | Listener.prototype.Event = function (newValue, oldValue) {
309 | //do something
310 | console.log('event is invoked');
311 | };
312 | return Listener;
313 | })();
314 |
315 | var Spy = (function () {
316 | function Spy() {
317 | this._partiesToNotify = [];
318 | }
319 | Spy.prototype.Subscribe = function (subscriber) {
320 | this._partiesToNotify.push(subscriber);
321 | };
322 |
323 | Spy.prototype.Unsubscribe = function (subscriber) {
324 | this._partiesToNotify.remove(subscriber);
325 | };
326 |
327 | Spy.prototype.SetPainKillers = function (painKillers) {
328 | this._painKillers = painKillers;
329 | for (var i = 0; i < this._partiesToNotify.length; i++) {
330 | this._partiesToNotify[i](painKillers);
331 | }
332 | };
333 | return Spy;
334 | })();
335 |
336 | var Player = (function () {
337 | function Player() {
338 | }
339 | Player.prototype.OnKingPainKillerChange = function (newPainKillerAmount) {
340 | //perform some action
341 | console.log(newPainKillerAmount + ' is pain point of the king');
342 | };
343 | return Player;
344 | })();
345 |
346 | var s = new Spy();
347 | var p = new Player();
348 | s.Subscribe(p.OnKingPainKillerChange);//p is subscriber
349 | s.SetPainKillers(12);
350 | ```
351 | ## State Machine 상태패턴
352 | 상태 패턴은 내부 상태를 추상화 하고 클래스로 구현된 상태들 사이의 메세지를 프록시로 전달 하는 상태 관리자(State Manager)를 가지는 것을 특징으로 한다.
353 |
354 | ```javascript
355 | var NaiveBanking = (function () {
356 | function NaiveBanking() {
357 | this.state = "";
358 | this.balance = 0;
359 | }
360 | NaiveBanking.prototype.NextState = function (action, amount) {
361 | if (this.state == "overdrawn" && action == "withdraw") {
362 | this.state = "on hold";
363 | }
364 | if (this.state == "on hold" && action != "deposit") {
365 | this.state = "on hold";
366 | }
367 | if (this.state == "good standing" && action == "withdraw" && amount <= this.balance) {
368 | this.balance -= amount;
369 | }
370 | if (this.state == "good standing" && action == "withdraw" && amount > this.balance) {
371 | this.balance -= amount;
372 | this.state = "overdrawn";
373 | }
374 | };
375 | return NaiveBanking;
376 | })();
377 |
378 | var BankAccountManager = (function () {
379 | function BankAccountManager() {
380 | this.currentState = new GoodStandingState(this);
381 | }
382 | BankAccountManager.prototype.Deposit = function (amount) {
383 | this.currentState.Deposit(amount);
384 | };
385 |
386 | BankAccountManager.prototype.Withdraw = function (amount) {
387 | this.currentState.Withdraw(amount);
388 | };
389 | BankAccountManager.prototype.addToBalance = function (amount) {
390 | this.balance += amount;
391 | };
392 | BankAccountManager.prototype.getBalance = function () {
393 | return this.balance;
394 | };
395 | BankAccountManager.prototype.moveToState = function (newState) {
396 | this.currentState = newState;
397 | };
398 | return BankAccountManager;
399 | })();
400 |
401 | var GoodStandingState = (function () {
402 | function GoodStandingState(manager) {
403 | this.manager = manager;
404 | }
405 | GoodStandingState.prototype.Deposit = function (amount) {
406 | this.manager.addToBalance(amount);
407 | };
408 | GoodStandingState.prototype.Withdraw = function (amount) {
409 | if (this.manager.getBalance() < amount) {
410 | this.manager.moveToState(new OverdrawnState(this.manager));
411 | }
412 |
413 | this.manager.addToBalance(-1 * amount);
414 | };
415 | return GoodStandingState;
416 | })();
417 |
418 | var OverdrawnState = (function () {
419 | function OverdrawnState(manager) {
420 | this.manager = manager;
421 | }
422 | OverdrawnState.prototype.Deposit = function (amount) {
423 | this.manager.addToBalance(amount);
424 | if (this.manager.getBalance() > 0) {
425 | this.manager.moveToState(new GoodStandingState(this.manager));
426 | }
427 | };
428 | OverdrawnState.prototype.Withdraw = function (amount) {
429 | this.manager.moveToState(new OnHold(this.manager));
430 | throw "Cannot withdraw money from an already overdrawn bank account";
431 | };
432 | return OverdrawnState;
433 | })();
434 |
435 | var OnHold = (function () {
436 | function OnHold(manager) {
437 | this.manager = manager;
438 | }
439 | OnHold.prototype.Deposit = function (amount) {
440 | this.manager.addToBalance(amount);
441 | throw "Your account is on hold and you must attend the bank to resolve the issue";
442 | };
443 | OnHold.prototype.Withdraw = function (amount) {
444 | throw "Your account is on hold and you must attend the bank to resolve the issue";
445 | };
446 | return OnHold;
447 | })();
448 | var goodStandingState = new GoodStandingState(new BankAccountManager());
449 |
450 |
451 | ```
452 | ## 템플릿 메소드 패턴
453 |
454 | ## 비지터 패턴
455 |
--------------------------------------------------------------------------------
/05_행동_패턴/READMD.md:
--------------------------------------------------------------------------------
1 | # 5. 행동 패턴
2 |
3 | [안도형](./DOHYUNG.md)
4 | [현진호](./JINHO.md)
5 |
--------------------------------------------------------------------------------
/05_행동_패턴/jaenam.md:
--------------------------------------------------------------------------------
1 | # CH5. 행동패턴
2 |
3 | ## 1. Chain of responsibility
4 |
5 | // https://ko.wikipedia.org/wiki/%EC%B1%85%EC%9E%84_%EC%97%B0%EC%87%84_%ED%8C%A8%ED%84%B4
6 |
7 | ```js
8 | const Allowable = Symbol('Allowable')
9 | const BASE = 500
10 |
11 | class PurchasePower {
12 | setSuccessor(successor) {
13 | this.successor = successor
14 | }
15 | processRequest (request, ClassName) {
16 | if(request.amount < this[Allowable]) {
17 | console.log(`${ClassName} will approve $${request.amount}`)
18 | } else if(!!this.successor) {
19 | this.successor.processRequest(request)
20 | }
21 | }
22 | }
23 |
24 | class ManagerPower extends PurchasePower {
25 | constructor () {
26 | super()
27 | this[Allowable] = 10 * BASE
28 | }
29 | processRequest (request) {
30 | super.processRequest(request, 'Manager')
31 | }
32 | }
33 |
34 | class DirectorPower extends PurchasePower {
35 | constructor () {
36 | super()
37 | this[Allowable] = 20 * BASE
38 | }
39 | processRequest (request) {
40 | super.processRequest(request, 'Director')
41 | }
42 | }
43 |
44 | class VicePresidentPower extends PurchasePower {
45 | constructor () {
46 | super()
47 | this[Allowable] = 40 * BASE
48 | }
49 | processRequest (request) {
50 | super.processRequest(request, 'Vice President')
51 | }
52 | }
53 |
54 | class PresidentPower extends PurchasePower {
55 | constructor () {
56 | super()
57 | this[Allowable] = 60 * BASE
58 | }
59 | processRequest (request) {
60 | if(request.amount < this[Allowable]) {
61 | console.log(`President will approve $${request.amount}`)
62 | } else {
63 | console.log(`Your request for $${rquest.amount} needs a board meeting!`)
64 | }
65 | }
66 | }
67 |
68 | const PurchaseRequest = (() => {
69 | const Amount = Symbol('Amount')
70 | return class {
71 | constructor (amount) {
72 | this[Amount] = amount
73 | }
74 | get amount () {
75 | return this[Amount]
76 | }
77 | set amount (amount) {
78 | this[Amount] = amount
79 | }
80 | }
81 | })()
82 |
83 | const manager = new ManagerPower()
84 | const director = new DirectorPower()
85 | const vp = new VicePresidentPower()
86 | const president = new PresidentPower()
87 | manager.setSuccessor(director)
88 | director.setSuccessor(vp)
89 | vp.setSuccessor(president)
90 |
91 | let n = 1
92 | while(n) {
93 | n = prompt('Enter the amount to check who should approve your expenditure.')
94 | manager.processRequest(new PurchaseRequest(n))
95 | }
96 | ```
97 |
98 | ## 2. Command
99 |
100 | https://ko.wikipedia.org/wiki/%EC%BB%A4%EB%A7%A8%EB%93%9C_%ED%8C%A8%ED%84%B4
101 |
102 | ```js
103 | class Switch {
104 | constructor (up, dn) {
105 | this.up = up
106 | this.dn = dn
107 | }
108 | flipUp () {
109 | this.up.execute()
110 | }
111 | flipDown () {
112 | this.dn.execute()
113 | }
114 | }
115 | class Light {
116 | turnOn () {
117 | console.log('The light is on')
118 | }
119 | turnOff () {
120 | console.log('The light is off')
121 | }
122 | }
123 | class TurnOnLightCommand {
124 | constructor (light) {
125 | this.theLight = light
126 | }
127 | execute () {
128 | this.theLight.turnOn()
129 | }
130 | }
131 | class TurnOffLightCommand {
132 | constructor (light) {
133 | this.theLight = light
134 | }
135 | execute () {
136 | this.theLight.turnOff()
137 | }
138 | }
139 |
140 | const light = new Light()
141 | const switchUp = new TurnOnLightCommand(light)
142 | const switchDown = new TurnOffLightCommand(light)
143 | const s = new Switch(switchUp, switchDown)
144 |
145 | s.flipUp()
146 | s.flipDown()
147 | ```
148 |
149 | ## 3. Interpreter
150 |
151 | ## 4. Iterator
152 |
153 | ## 5. Mediator
154 |
155 | http://egloos.zum.com/iilii/v/4850510
156 |
157 | ```js
158 | class ControlTower {
159 | constructor () {
160 | this.timer = null
161 | this.queue = []
162 | this.isOnSleep = false
163 | }
164 | _next () {
165 | this.isOnSleep = false
166 | const next = this.queue.shift()
167 | if(!next) {
168 | return
169 | }
170 | next()
171 | this._execute()
172 | }
173 | _sleep (ms) {
174 | this._execute(() => {
175 | clearTimeout(this.timer)
176 | this.isOnSleep = true
177 | this.timer = setTimeout(() => this._next(), ms)
178 | })
179 | }
180 | _execute (func) {
181 | if(func) {
182 | this.queue.push(func)
183 | }
184 | if(!this.isOnSleep) {
185 | this._next()
186 | }
187 | }
188 | land (airplane) {
189 | this._execute(() => {
190 | console.log(`${airplane.name} 비행기 - 착륙허가`)
191 | })
192 | this._sleep(600)
193 | this._execute(() => {
194 | console.log(`${airplane.name} 비행기 - 착륙완료`)
195 | })
196 | this._sleep(300)
197 | }
198 | }
199 |
200 | class Airplane {
201 | constructor (tower, name) {
202 | this.tower = tower
203 | this.name = name
204 | }
205 | requestToLand () {
206 | console.log(`${this.name} 비행기 - 착륙요청!!`)
207 | this.tower.land(this)
208 | }
209 | }
210 |
211 | const tower = new ControlTower()
212 | const airplanes = new Array(10).fill(0).map((v, i)=> new Airplane(tower, i+1))
213 | airplanes.forEach((v, i) => setTimeout(() => v.requestToLand(), 500 * i))
214 | ```
215 |
216 |
217 | ## 6. Memento
218 |
219 | ```js
220 | class Originator {
221 | constructor (memento) {
222 | this.memento = memento
223 | }
224 | set (state) {
225 | this.state = state
226 | this.memento.save(state)
227 | }
228 | undo () {
229 | this.state = this.memento.undo()
230 | }
231 | redo () {
232 | this.state = this.memento.redo()
233 | }
234 | getState () {
235 | return this.state
236 | }
237 | }
238 | class Memento {
239 | constructor () {
240 | this.store = ['']
241 | this.currentIndex = 0
242 | }
243 | save (state) {
244 | this.store.length = this.currentIndex + 1
245 | this.store.push(state)
246 | if(this.store.length > 5) {
247 | this.store = this.store.slice(-5)
248 | }
249 | this.currentIndex = this.store.length - 1
250 |
251 | }
252 | undo () {
253 | this.currentIndex = this.currentIndex > 0 ? this.currentIndex - 1 : 0
254 | return this.store[this.currentIndex]
255 | }
256 | redo () {
257 | this.currentIndex = this.currentIndex < this.store.length - 2 ? this.currentIndex + 1 : this.store.length - 1
258 | return this.store[this.currentIndex]
259 | }
260 | }
261 |
262 | const savedStates = []
263 | const originator = new Originator(new Memento())
264 | originator.set('State1')
265 | console.log(originator.getState())
266 | originator.set('State2')
267 | originator.set('State3')
268 | originator.set('State4')
269 | originator.set('State5')
270 | console.log(originator.getState())
271 | originator.undo()
272 | originator.undo()
273 | originator.set('State6')
274 | originator.set('State7')
275 | originator.set('State8')
276 | console.log(originator.getState())
277 | originator.undo()
278 | originator.undo()
279 | originator.undo()
280 | originator.redo()
281 | console.log(originator.getState())
282 | ```
283 |
284 | ## 7. Observer
285 |
286 | http://flowarc.tistory.com/entry/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-%EC%98%B5%EC%A0%80%EB%B2%84-%ED%8C%A8%ED%84%B4Observer-Pattern
287 |
288 | ```js
289 | class NewsMachine {
290 | constructor () {
291 | this.observers = new Set()
292 | this.title = null
293 | this.news = null
294 | }
295 | add (observer) {
296 | this.observers.add(observer)
297 | }
298 | delete (observer) {
299 | this.observers.delete(observer)
300 | }
301 | notifyObservers () {
302 | for(let o of this.observers) {
303 | o.update(this.title, this.news)
304 | }
305 | }
306 | setNewsInfo (title, news) {
307 | this.title = title
308 | this.news = news
309 | this.notifyObservers()
310 | }
311 | getTitle () {
312 | return this.title
313 | }
314 | getNews () {
315 | return this.news
316 | }
317 | }
318 |
319 | class Subscriber {
320 | subscribe (publisher) {
321 | publisher.add(this)
322 | this.publisher = publisher
323 | }
324 | unsubscribe () {
325 | this.publisher.delete(this)
326 | this.publisher = null
327 | }
328 | }
329 |
330 | class AnnualSubscriber extends Subscriber {
331 | constructor (publisher) {
332 | super()
333 | super.subscribe(publisher)
334 | }
335 | update (title, news) {
336 | console.log(`\n\n오늘의 뉴스\n======================\n\n${title}\n-----------\n${news}`)
337 | }
338 | }
339 | class EventSubscriber extends Subscriber {
340 | constructor (publisher) {
341 | super()
342 | super.subscribe(publisher)
343 | }
344 | update (title, news) {
345 | console.log(`\n\n이벤트 유저\n=========\n\n${title}\n-----\n${news}`)
346 | }
347 | }
348 |
349 | const news = new NewsMachine()
350 | const as = new AnnualSubscriber(news)
351 | const es = new EventSubscriber(news)
352 | news.setNewsInfo('오늘 한파', '전국 영하 18도 입니다.')
353 | es.unsubscribe()
354 | news.setNewsInfo('벚꽃 축제합니다', '벚꽃 보러 가즈아~')
355 | ```
356 |
357 | ## 8. State
358 |
359 | ## 9. Strategy
360 |
361 | ## 10. Template Method
362 |
363 | ## 11. Visitor
364 |
--------------------------------------------------------------------------------
/05_행동_패턴/jina.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # Behavioral Pattern -행동 패턴
6 | > 클래스와 객체들이 상호작용하는 방법 및 역할을 분담하는 방법과 관련된 패턴
7 | ## 1. Strategy Pattern
8 | > 상속을 위한 패턴 사용시 자식객체의 변화가 자주 발생시, 종류가 다양해지고 자식 객체마다 오버라이딩 해주어야 하기 때문에 유지보수 측면에서 좋지 않다.
9 | > 교환 가능한 행동을 캡슐화하고 위임을 통해 어떤 행동을 사용할 지 결정한다.
10 | > 즉 변하는 부분 캡슐화하여 변화하는 전략에 대응한다.
11 | >
12 | ```js
13 | var Shipping = function() {
14 | this.company = "";
15 | };
16 |
17 | Shipping.prototype = {
18 | setStrategy: function(company) {
19 | this.company = company;
20 | },
21 |
22 | calculate: function(package) {
23 | return this.company.calculate(package);
24 | }
25 | };
26 |
27 | var UPS = function() {
28 | this.calculate = function(package) {
29 | // calculations...
30 | return "$45.95";
31 | }
32 | };
33 |
34 | var USPS = function() {
35 | this.calculate = function(package) {
36 | // calculations...
37 | return "$39.40";
38 | }
39 | };
40 |
41 | var Fedex = function() {
42 | this.calculate = function(package) {
43 | // calculations...
44 | return "$43.20";
45 | }
46 | };
47 |
48 | // log helper
49 |
50 | var log = (function() {
51 | var log = "";
52 |
53 | return {
54 | add: function(msg) { log += msg + "\n"; },
55 | show: function() { alert(log); log = ""; }
56 | }
57 | })();
58 |
59 | function run() {
60 | var package = { from: "76712", to: "10012", weigth: "lkg" };
61 |
62 | // the 3 strategies
63 |
64 | var ups = new UPS();
65 | var usps = new USPS();
66 | var fedex = new Fedex();
67 |
68 | var shipping = new Shipping();
69 |
70 | shipping.setStrategy(ups);
71 | log.add("UPS Strategy: " + shipping.calculate(package));
72 | shipping.setStrategy(usps);
73 | log.add("USPS Strategy: " + shipping.calculate(package));
74 | shipping.setStrategy(fedex);
75 | log.add("Fedex Strategy: " + shipping.calculate(package));
76 |
77 | log.show();
78 | }
79 |
80 | ```
81 |
82 | ## 2. Mediator Pattern : 중재자 패턴
83 | > 다대다 관계의 클래스에서 객체가 상호작용하는 방식을 캡슐화하여
84 | > 객체 그룹에 대해 유일한 변경장소로 동작하도록 한다.
85 | >
86 | >
87 | ```js
88 | var Participant = function(name) {
89 | this.name = name;
90 | this.chatroom = null;
91 | };
92 |
93 | Participant.prototype = {
94 | send: function(message, to) {
95 | this.chatroom.send(message, this, to);
96 | },
97 | receive: function(message, from) {
98 | log.add(from.name + " to " + this.name + ": " + message);
99 | }
100 | };
101 |
102 | var Chatroom = function() {
103 | var participants = {};
104 |
105 | return {
106 |
107 | register: function(participant) {
108 | participants[participant.name] = participant;
109 | participant.chatroom = this;
110 | },
111 |
112 | send: function(message, from, to) {
113 | if (to) { // single message
114 | to.receive(message, from);
115 | } else { // broadcast message
116 | for (key in participants) {
117 | if (participants[key] !== from) {
118 | participants[key].receive(message, from);
119 | }
120 | }
121 | }
122 | }
123 | };
124 | };
125 |
126 | // log helper
127 |
128 | var log = (function() {
129 | var log = "";
130 |
131 | return {
132 | add: function(msg) { log += msg + "\n"; },
133 | show: function() { alert(log); log = ""; }
134 | }
135 | })();
136 |
137 | function run() {
138 | var yoko = new Participant("Yoko");
139 | var john = new Participant("John");
140 | var paul = new Participant("Paul");
141 | var ringo = new Participant("Ringo");
142 |
143 | var chatroom = new Chatroom();
144 | chatroom.register(yoko);
145 | chatroom.register(john);
146 | chatroom.register(paul);
147 | chatroom.register(ringo);
148 |
149 | yoko.send("All you need is love.");
150 | yoko.send("I love you John.");
151 | john.send("Hey, no need to broadcast", yoko);
152 | paul.send("Ha, I heard that!");
153 | ringo.send("Paul, what do you think?", paul);
154 |
155 | log.show();
156 | }
157 | ```
158 |
159 | ## 3. Command Pattern : 명령패턴
160 | > 동작을 객체로 캡슐화한 명령 그자체.
161 | >
162 |
163 | ```js
164 | function add(x, y) { return x + y; }
165 | function sub(x, y) { return x - y; }
166 | function mul(x, y) { return x * y; }
167 | function div(x, y) { return x / y; }
168 |
169 | var Command = function (execute, undo, value) {
170 | this.execute = execute;
171 | this.undo = undo;
172 | this.value = value;
173 | }
174 |
175 | var AddCommand = function (value) {
176 | return new Command(add, sub, value);
177 | };
178 |
179 | var SubCommand = function (value) {
180 | return new Command(sub, add, value);
181 | };
182 |
183 | var MulCommand = function (value) {
184 | return new Command(mul, div, value );
185 | };
186 |
187 | var DivCommand = function (value) {
188 | return new Command(div, mul, value);
189 | };
190 |
191 | var Calculator = function () {
192 | var current = 0;
193 | var commands = [];
194 |
195 | function action(command) {
196 | var name = command.execute.toString().substr(9, 3);
197 | return name.charAt(0).toUpperCase() + name.slice(1);
198 | }
199 |
200 | return {
201 | execute: function (command) {
202 | current = command.execute(current, command.value);
203 | commands.push(command);
204 | log.add(action(command) + ": " + command.value);
205 | },
206 |
207 | undo: function () {
208 | var command = commands.pop();
209 | current = command.undo(current, command.value);
210 | log.add("Undo " + action(command) + ": " + command.value);
211 | },
212 |
213 | getCurrentValue: function () {
214 | return current;
215 | }
216 | }
217 | }
218 |
219 | // log helper
220 |
221 | var log = (function () {
222 | var log = "";
223 |
224 | return {
225 | add: function (msg) { log += msg + "\n"; },
226 | show: function () { alert(log); log = ""; }
227 | }
228 | })();
229 |
230 | function run() {
231 | var calculator = new Calculator();
232 |
233 | // issue commands
234 |
235 | calculator.execute(new AddCommand(100));
236 | calculator.execute(new SubCommand(24));
237 | calculator.execute(new MulCommand(6));
238 | calculator.execute(new DivCommand(2));
239 |
240 | // reverse last two commands
241 |
242 | calculator.undo();
243 | calculator.undo();
244 |
245 | log.add("\nValue: " + calculator.getCurrentValue());
246 | log.show();
247 | }
248 |
249 | ```
250 |
251 | ## 4. State Pattern - 상태 패턴
252 | > 상태패턴은 각 개체가 특정 상태를 나타내는 제한된 객체 집합에 상태 별 논리를 제공. 상태별 객체 자체 송성 집합과 메서드를 갖는다. 전환이 발생될때 상태 객체가 다른 상태 객체로 바뀌는 것.
253 | >
254 | >
255 | ```js
256 | var TrafficLight = function () {
257 | var count = 0;
258 | var currentState = new Red(this);
259 |
260 | this.change = function (state) {
261 | // limits number of changes
262 | if (count++ >= 10) return;
263 | currentState = state;
264 | currentState.go();
265 | };
266 |
267 | this.start = function () {
268 | currentState.go();
269 | };
270 | }
271 |
272 | var Red = function (light) {
273 | this.light = light;
274 |
275 | this.go = function () {
276 | log.add("Red --> for 1 minute");
277 | light.change(new Green(light));
278 | }
279 | };
280 |
281 | var Yellow = function (light) {
282 | this.light = light;
283 |
284 | this.go = function () {
285 | log.add("Yellow --> for 10 seconds");
286 | light.change(new Red(light));
287 | }
288 | };
289 |
290 | var Green = function (light) {
291 | this.light = light;
292 |
293 | this.go = function () {
294 | log.add("Green --> for 1 minute");
295 | light.change(new Yellow(light));
296 | }
297 | };
298 |
299 | // log helper
300 |
301 | var log = (function () {
302 | var log = "";
303 |
304 | return {
305 | add: function (msg) { log += msg + "\n"; },
306 | show: function () { alert(log); log = ""; }
307 | }
308 | })();
309 |
310 | function run() {
311 | var light = new TrafficLight();
312 | light.start();
313 |
314 | log.show();
315 | }
316 | ```
317 |
--------------------------------------------------------------------------------
/05_행동_패턴/junghyun.md:
--------------------------------------------------------------------------------
1 | # 5. Behavioral Patterns
2 | ## Chain of Responsibility Pattern
3 | 명령 객체와 일련의 처리 객체를 포함하는 디자인 패턴이다. 각각의 처리 객체는 명령 객체를 처리할 수 있는 연산의 집합이고, 체인 안의 처리 객체가 핸들할 수 없는 명령은 다음 처리 객체로 넘겨진다. 이 작동방식은 새로운 처리 객체부터 체인의 끝까지 다시 반복된다.
4 | ```js
5 | class AbstractLogger {
6 | constructor() {
7 | if (this.constructor === AbstractLogger) {
8 | // Abstract class can not be constructed.
9 | throw new TypeError("Can not construct abstract class.");
10 | }
11 | }
12 |
13 | setNextLogger(nextLogger) {
14 | this.nextLogger = nextLogger;
15 | }
16 |
17 | logMessage(level, message) {
18 | if (this.level <= level) {
19 | this.write(message);
20 | }
21 | if (this.nextLogger != null) {
22 | this.nextLogger.logMessage(level, message);
23 | }
24 | }
25 |
26 | // abstarct method
27 | write(message) {
28 | // The child has implemented this method but also called `super.foo()`.
29 | throw new TypeError("Do not call abstract method foo from child.");
30 | }
31 |
32 | static get INFO() {
33 | return 1;
34 | }
35 |
36 | static get DEBUG() {
37 | return 2;
38 | }
39 |
40 | static get ERROR() {
41 | return 3;
42 | }
43 | }
44 |
45 | class ConsoleLogger extends AbstractLogger {
46 | constructor(level) {
47 | super();
48 | this.level = level;
49 | }
50 |
51 | write(message) {
52 | console.log("Standard Console::Logger: " + message);
53 | }
54 | }
55 |
56 | class ErrorLogger extends AbstractLogger {
57 | constructor(level) {
58 | super();
59 | this.level = level;
60 | }
61 |
62 | write(message) {
63 | console.log("Error Console::Logger: " + message);
64 | }
65 | }
66 |
67 | class FileLogger extends AbstractLogger {
68 | constructor(level) {
69 | super();
70 | this.level = level;
71 | }
72 |
73 | write(message) {
74 | console.log("FileLogger::Logger: " + message);
75 | }
76 | }
77 |
78 | // Main
79 | (function () {
80 | function getChainOfLoggers() {
81 | // create, instance
82 | const errorLogger = new ErrorLogger(AbstractLogger.ERROR);
83 | const fileLogger = new FileLogger(AbstractLogger.DEBUG);
84 | const consoleLogger = new ConsoleLogger(AbstractLogger.INFO);
85 |
86 | // apply, chain
87 | errorLogger.setNextLogger(fileLogger);
88 | fileLogger.setNextLogger(consoleLogger);
89 |
90 | return errorLogger
91 | }
92 |
93 | const loggerChain = getChainOfLoggers();
94 |
95 | loggerChain.logMessage(AbstractLogger.INFO, "This is an information.");
96 | loggerChain.logMessage(AbstractLogger.DEBUG, "This is an debug level information.");
97 | loggerChain.logMessage(AbstractLogger.ERROR, "This is an error information.");
98 | })()
99 | ```
100 |
101 | ## Command Pattern
102 | 요청을 객체의 형태로 캡슐화하여 사용자가 보낸 요청을 나중에 이용할 수 있도록 매서드 이름, 매개변수 등 요청에 필요한 정보를 저장 또는 로깅, 취소할 수 있게 하는 패턴이다.
103 |
104 | ```js
105 | // request
106 | class Stock {
107 | constructor(name, quantity) {
108 | this.name = name;
109 | this.quantity = quantity;
110 | }
111 |
112 | buy() {
113 | console.log("Stock [ Name: " + this.name + ", Quantity: " + this.quantity + " ]bought");
114 | }
115 |
116 | sell() {
117 | console.log("Stock [ Name: " + this.name + ", Quantity: " + this.quantity + " ]sold");
118 | }
119 | }
120 |
121 | // concrete command classes
122 | class BuyStock {
123 | constructor(stock) {
124 | this.stock = stock;
125 | }
126 |
127 | execute() {
128 | this.stock.buy();
129 | }
130 | }
131 |
132 | class SellStock {
133 | constructor(stock) {
134 | this.stock = stock;
135 | }
136 |
137 | execute() {
138 | this.stock.sell();
139 | }
140 | }
141 |
142 | // invoker class
143 | class Broker {
144 | constructor() {
145 | this.orderList = [];
146 | }
147 |
148 | takeOrder(order) {
149 | this.orderList.push(order);
150 | }
151 |
152 | placeOrders() {
153 | this.orderList.forEach((o) => {
154 | o.execute();
155 | })
156 | this.orderList = [];
157 | }
158 | }
159 |
160 | // Main
161 | (function () {
162 | const stock = new Stock('A', 10);
163 |
164 | const buyStockOrder = new BuyStock(stock);
165 | const sellStockOrder = new SellStock(stock);
166 |
167 | const broker = new Broker();
168 | broker.takeOrder(buyStockOrder);
169 | broker.takeOrder(sellStockOrder);
170 |
171 | broker.placeOrders();
172 | })()
173 | ```
174 |
175 | ## Interpreter Pattern
176 | 새로운 표현 및 문법 적용, like SQL.
177 |
178 | ## Iterator Pattern
179 | 기본 표현을 알 필요없이 순차적 요소를 접근하는 패턴.
180 |
181 | ```js
182 | function makeIterator(array) {
183 | let nextIndex = 0;
184 |
185 | return {
186 | next: () => {
187 | return nextIndex < array.length ?
188 | { value: array[nextIndex++], done: false } :
189 | { done: true };
190 | }
191 | };
192 | }
193 |
194 | let it = makeIterator(['yo', 'ya']);
195 | console.log(it.next().value); // 'yo'
196 | console.log(it.next().value); // 'ya'
197 | console.log(it.next().done); // true
198 | ```
199 |
200 | ## Mediator Pattern
201 | 여러 객체 또는 클래스 사이의 통신의 복잡성을 줄이는 패턴.
202 | 객체간의 통신은 중재자 객체를 통해 진행된다.
203 | ```js
204 | // mediator class
205 | class ChatRoom {
206 | static showMessage(user, message) {
207 | console.log(new Date().toString() + " [" + user.name + "] : " + message);
208 | }
209 | }
210 |
211 | class User {
212 | constructor(name) {
213 | this._name = name;
214 | }
215 |
216 | sendMessage(message) {
217 | ChatRoom.showMessage(this, message);
218 | }
219 |
220 | get name() {
221 | return this._name;
222 | }
223 | }
224 |
225 | (function () {
226 | const robert = new User("Robert");
227 | const john = new User("John");
228 |
229 | robert.sendMessage("Hi! John!");
230 | john.sendMessage("Hello! Robert!");
231 | })()
232 | ```
233 |
234 | ## Memento Pattern
235 | 객체를 이전 상태로 되돌릴 수 있는 기능을 제공하는 패턴.
236 | 오리지네이터(originator), 케어테이커(caretaker), 메멘토(memento).
237 | originator - 객체 상태 저장 및 생성.
238 | caretaker - 객체 상태를 되돌리기 위한 책임을 가짐.
239 | memento - 되돌리기 위한 객체의 상태.
240 | ```js
241 | class Memento {
242 | constructor(state) {
243 | this._state = state;
244 | }
245 |
246 | get state() {
247 | return this._state;
248 | }
249 | }
250 |
251 | class Originator {
252 | saveStateToMemento() {
253 | return new Memento(this._state);
254 | }
255 |
256 | restoreStateFromMemento(memento) {
257 | this._state = memento.state;
258 | }
259 |
260 | set state(state) {
261 | this._state = state;
262 | }
263 |
264 | get state() {
265 | return this._state;
266 | }
267 | }
268 |
269 | class CareTaker {
270 | constructor() {
271 | this._mementoList = [];
272 | }
273 |
274 | add(state) {
275 | this.mementoList.push(state);
276 | }
277 |
278 | get(index) {
279 | return this._mementoList[index];
280 | }
281 |
282 | set mementoList(state) {
283 | this._mementoList = state;
284 | }
285 |
286 | get mementoList() {
287 | return this._mementoList;
288 | }
289 | }
290 |
291 | (function () {
292 | const originator = new Originator();
293 | const careTaker = new CareTaker();
294 |
295 | originator.state = "State #1";
296 | originator.state = "State #2";
297 | careTaker.add(originator.saveStateToMemento());
298 |
299 | originator.state = "State #3";
300 | careTaker.add(originator.saveStateToMemento());
301 |
302 | originator.state = "State #4";
303 | console.log("Current State: " + originator.state);
304 |
305 | originator.restoreStateFromMemento(careTaker.get(0));
306 | console.log("First saved State: " + originator.state);
307 | originator.restoreStateFromMemento(careTaker.get(1));
308 | console.log("Second saved State: " + originator.state);
309 | })()
310 | ```
311 |
312 | ## Obsever Pattern
313 | 일대다 관계의 객체들 중 하나의 객체가 수정되었을 때, 자동으로 모든 객체한테 변화를 알려준다.
314 |
315 | ```js
316 | class Subject {
317 | constructor() {
318 | this._observers = [];
319 | }
320 |
321 | attach(observer) {
322 | this._observers.push(observer);
323 | }
324 |
325 | notifyAllObservers() {
326 | this._observers.forEach((ob) => {
327 | ob.update();
328 | })
329 | }
330 |
331 | get state() {
332 | return this._state;
333 | }
334 |
335 | set state(state) {
336 | this._state = state;
337 | this.notifyAllObservers();
338 | }
339 | }
340 |
341 | class Observer {
342 | constructor() {
343 | if (this.constructor === Observer) {
344 | // Abstract class can not be constructed.
345 | throw new TypeError("Can not construct abstract class.");
346 | }
347 | }
348 |
349 | update() {
350 | throw new TypeError("Do not call abstract method foo from child.");
351 | }
352 | }
353 |
354 | class HexaObserver extends Observer {
355 | constructor(subject) {
356 | super();
357 | this.subject = subject;
358 | this.subject.attach(this);
359 | }
360 |
361 | update() {
362 | console.log("Hexa String: " + this.subject.state.toString(16));
363 | }
364 | }
365 |
366 | class OctalObserver extends Observer {
367 | constructor(subject) {
368 | super();
369 | this.subject = subject;
370 | this.subject.attach(this);
371 | }
372 |
373 | update() {
374 | console.log("Octal String: " + this.subject.state.toString(8));
375 | }
376 | }
377 |
378 | class BinaryObserver extends Observer {
379 | constructor(subject) {
380 | super();
381 | this.subject = subject;
382 | this.subject.attach(this);
383 | }
384 |
385 | update() {
386 | console.log("Binary String: " + this.subject.state.toString(2));
387 | }
388 | }
389 |
390 | (function () {
391 | const subject = new Subject();
392 |
393 | new HexaObserver(subject);
394 | new OctalObserver(subject);
395 | new BinaryObserver(subject);
396 |
397 | console.log("First state change: 15");
398 | subject.state = 15;
399 | console.log("Second state change: 10");
400 | subject.state = 10;
401 | })()
402 | ```
403 |
404 | ## State Pattren
405 | 각 상태를 클래스로 분할하여 단순화한다.
406 |
407 | ```js
408 | class StartState {
409 | doAction(context) {
410 | console.log("Player is in start state");
411 | context.state = this;
412 | }
413 |
414 | toString() {
415 | return "Start State";
416 | }
417 | }
418 |
419 | class StopState {
420 | doAction(context) {
421 | console.log("Player is in stop state");
422 | context.state = this;
423 | }
424 |
425 | toString() {
426 | return "Stop State";
427 | }
428 | }
429 |
430 | class Context {
431 | constructor() {
432 | this._state = null;
433 | }
434 |
435 | get state() {
436 | return this._state;
437 | }
438 |
439 | set state(state) {
440 | this._state = state;
441 | }
442 | }
443 |
444 | (function () {
445 | const context = new Context();
446 |
447 | const startState = new StartState();
448 | startState.doAction(context);
449 |
450 | console.log(context.state.toString());
451 |
452 | const stopState = new StopState();
453 | stopState.doAction(context);
454 |
455 | console.log(context.state.toString());
456 | })()
457 | ```
458 |
459 | ## Strategy Pattern
460 | 런타임 환경에서 알고리즘을 변경할 수 있다.
461 | ```js
462 | class Context {
463 | constructor(strategy) {
464 | this._strategy = strategy;
465 | }
466 |
467 | executeStrategy(a, b) {
468 | return this.strategy.doOperation(a, b);
469 | }
470 |
471 | get strategy() {
472 | return this._strategy;
473 | }
474 |
475 | set strategy(strategy) {
476 | this._strategy = strategy;
477 | }
478 | }
479 |
480 | class OperationAdd {
481 | doOperation(a, b) {
482 | return a + b;
483 | }
484 | }
485 |
486 | class OperationSubstract {
487 | doOperation(a, b) {
488 | return a - b;
489 | }
490 | }
491 |
492 | class OperationMultiply {
493 | doOperation(a, b) {
494 | return a * b;
495 | }
496 | }
497 |
498 | (function () {
499 | let context = new Context(new OperationAdd());
500 | console.log("10 + 5 = " + context.executeStrategy(10, 5));
501 |
502 | context = new Context(new OperationSubstract());
503 | console.log("10 - 5 = " + context.executeStrategy(10, 5));
504 |
505 | context = new Context(new OperationMultiply());
506 | console.log("10 * 5 = " + context.executeStrategy(10, 5));
507 | })()
508 | ```
509 |
510 | ## Template Pattern
511 | 알고리즘의 일부분만 공유하고, 일부분만 다른 방법으로 구현하는 방식.
512 | 알고리즘의 일부만 구현하고 다른 부분은 나중에 다른 클래스에서 이 부분을 재정의하여 확장함(추상화)
513 | ```js
514 | class Game {
515 | constructor() {
516 | if (this.constructor === Game) {
517 | return TypeError("Can not construct abstract class");
518 | }
519 | }
520 |
521 | initialize() {
522 | throw new TypeError("Do not call abstract method from child.");
523 | }
524 |
525 | startPlay() {
526 | throw new TypeError("Do not call abstract method from child.");
527 | }
528 |
529 | endPlay() {
530 | throw new TypeError("Do not call abstract method from child.");
531 | }
532 |
533 | play() {
534 | //initialize the game
535 | this.initialize();
536 |
537 | //start game
538 | this.startPlay();
539 |
540 | //end game
541 | this.endPlay();
542 | }
543 | }
544 |
545 | class Cricket extends Game {
546 | initialize() {
547 | console.log("Cricket Game Initialized! Start playing.");
548 | }
549 |
550 | startPlay() {
551 | console.log("Cricket Game Started. Enjoy the game!");
552 | }
553 |
554 | endPlay() {
555 | console.log("Cricket Game Finished!");
556 | }
557 | }
558 |
559 | class Football extends Game {
560 | initialize() {
561 | console.log("Football Game Initialized! Start playing.");
562 | }
563 |
564 | startPlay() {
565 | console.log("Football Game Started. Enjoy the game!");
566 | }
567 |
568 | endPlay() {
569 | console.log("Football Game Finished!");
570 | }
571 | }
572 |
573 | (function () {
574 | let game = new Cricket();
575 | game.play();
576 |
577 | game = new Football();
578 | game.play();
579 | })()
580 | ```
581 |
582 | ## Visitor Pattern
583 | 알고리즘을 객체 구조에서 분리시키는 디자인 패턴이다. 이렇게 분리를 하면 구조를 수정하지 않고도 실질적으로 새로운 동작을 기존의 객체 구조에 추가할 수 있게 된다.
584 | ```js
585 | class ComputerPartDisplayVisitor {
586 | visit(part) {
587 | switch (part.constructor) {
588 | case Computer:
589 | console.log("Displaying Computer.")
590 | break;
591 | case Mouse:
592 | console.log("Displaying Mouse.")
593 | break;
594 | case Keyboard:
595 | console.log("Displaying Keyboard.")
596 | break;
597 | case Monitor:
598 | console.log("Displaying Monitor.")
599 | break;
600 | }
601 | }
602 | }
603 |
604 | class Computer {
605 | constructor(parts) {
606 | this.parts = parts;
607 | }
608 |
609 | accept(visitor) {
610 | this.parts.forEach((p) => {
611 | p.accept(visitor);
612 | })
613 | visitor.visit(this);
614 | }
615 | }
616 |
617 | class Mouse {
618 | accept(visitor) {
619 | visitor.visit(this);
620 | }
621 | }
622 |
623 | class Keyboard {
624 | accept(visitor) {
625 | visitor.visit(this);
626 | }
627 | }
628 |
629 | class Monitor {
630 | accept(visitor) {
631 | visitor.visit(this);
632 | }
633 | }
634 |
635 | (function () {
636 | const computer = new Computer([new Mouse(), new Keyboard(), new Monitor()]);
637 | computer.accept(new ComputerPartDisplayVisitor());
638 | })()
639 | ```
--------------------------------------------------------------------------------
/06_함수형_프로그래밍/BHKIM.md:
--------------------------------------------------------------------------------
1 | # 6. 함수형 프로그래밍
2 |
3 | 1. 함수 전달
4 | 2. 필터와 파이프
5 | 3. 어큐뮬레이터
6 | 4. 메모이제이션
7 | 5. 불변성
8 | 6. 지연 인스턴스 생성
9 |
10 | ### 함수 전달 (Function passing)
11 | ```js
12 | class HamiltonianTour {
13 | constructor(options) {
14 | this.options = options;
15 | }
16 | StartTour() {
17 | if (this.options.onTourStart && typeof (this.options.onTourStart) === "function")
18 | this.options.onTourStart();
19 | this.VisitAttraction("King's Landing");
20 | this.VisitAttraction("Winterfell");
21 | this.VisitAttraction("Mountains of Dorne");
22 | this.VisitAttraction("Eyrie");
23 | if (this.options.onTourCompletion && typeof (this.options.onTourCompletion) === "function")
24 | this.options.onTourCompletion();
25 | }
26 | VisitAttraction(AttractionName) {
27 | if (this.options.onEntryToAttraction && typeof (this.options.onEntryToAttraction) === "function")
28 | this.options.onEntryToAttraction(AttractionName);
29 | //do whatever one does in a Attraction
30 | if (this.options.onExitFromAttraction && typeof (this.options.onExitFromAttraction) === "function")
31 | this.options.onExitFromAttraction(AttractionName);
32 | }
33 | }
34 | class HamiltonianTourOptions {
35 | }
36 | var tour = new HamiltonianTour({
37 | onEntryToAttraction: function(cityname) {
38 | console.log("I'm delighted to be in " + cityname);
39 | }
40 | });
41 | tour.StartTour();
42 | ```
43 |
44 | ### 필터와 파이프 (Filters and pipes)
45 | ```js
46 | Array.prototype.where = function (inclusionTest) {
47 | var results = [];
48 | for (var i = 0; i < this.length; i++) {
49 | if (inclusionTest(this[i]))
50 | results.push(this[i]);
51 | }
52 | return results;
53 | };
54 | Array.prototype.select = function (projection) {
55 | var results = [];
56 | for (var i = 0; i < this.length; i++) {
57 | results.push(projection(this[i]));
58 | }
59 | return results;
60 | };
61 | const children = [{ id: 1, Name: "Rob" },
62 | { id: 2, Name: "Sansa" },
63 | { id: 3, Name: "Arya" },
64 | { id: 4, Name: "Brandon" },
65 | { id: 5, Name: "Rickon" }];
66 | var filteredChildren = children.where(function (x) {
67 | return x.id % 2 == 0;
68 | }).select(function (x) {
69 | return x.Name;
70 | });
71 | console.dir(children);
72 | console.dir(filteredChildren);
73 | ```
74 |
75 | ### 어큐뮬레이터 (Accumulators)
76 | ```js
77 | class TaxCollector {
78 | collect(items, value, projection) {
79 | if (items.length > 1)
80 | return projection(items[0]) + this.collect(items.slice(1), value, projection);
81 | return projection(items[0]);
82 | }
83 | }
84 | var peasants = [{ name: "Jory Cassel", moneyOwed: 11, bankBalance: 50 },
85 | { name: "Vardis Egen", moneyOwed: 15, bankBalance: 20 }];
86 | var collector = new TaxCollector();
87 | console.log(collector.collect(peasants, 0, (item) => Math.min(item.moneyOwed, item.bankBalance)));
88 | ```
89 |
90 | ### 메모이제이션 (Memoization)
91 | ```js
92 | class Fibonacci {
93 | constructor() {
94 | this.memorizedValues = [];
95 | }
96 | NaieveFib(n) {
97 | if (n == 0)
98 | return 0;
99 | if (n <= 2)
100 | return 1;
101 | return this.NaieveFib(n - 1) + this.NaieveFib(n - 2);
102 | }
103 | MemetoFib(n) {
104 | if (n == 0)
105 | return 0;
106 | if (n <= 2)
107 | return 1;
108 | if (!this.memorizedValues[n])
109 | this.memorizedValues[n] = this.MemetoFib(n - 1) + this.MemetoFib(n - 2);
110 | return this.memorizedValues[n];
111 | }
112 | }
113 | var fib = new Fibonacci();
114 | console.log(fib.MemetoFib(50));
115 | ```
116 |
117 | ### 불변성 (Immutability)
118 | ```js
119 | var numberOfQueens = 1;
120 | const numberOfKings = 1;
121 | numberOfQueens++;
122 | numberOfKings++;
123 | console.log(numberOfQueens);
124 | console.log(numberOfKings);
125 |
126 | var consts = Object.freeze({ pi: 3.141 });
127 | consts.pi = 7;
128 |
129 | var t = Object.create(Object.prototype, { value: { writabe: false, value: 10} });
130 | t.value = 7;
131 | console.log(t.value);
132 | ```
133 |
134 | ### 지연 인스턴스 생성 (Lazy instantiation)
135 | ```js
136 | class Bakery {
137 | constructor() {
138 | this.requiredBreads = [];
139 | }
140 | orderBreadType(breadType) {
141 | this.requiredBreads.push(breadType);
142 | }
143 | pickUpBread(breadType) {
144 | console.log("Picup of bread " + breadType + " requested");
145 | if (!this.breads) {
146 | this.createBreads();
147 | }
148 | for (var i = 0; i < this.breads.length; i++) {
149 | if (this.breads[i].breadType == breadType)
150 | return this.breads[i];
151 | }
152 | }
153 | createBreads() {
154 | this.breads = [];
155 | for (var i = 0; i < this.requiredBreads.length; i++) {
156 | this.breads.push(new Bread(this.requiredBreads[i]));
157 | }
158 | }
159 | }
160 | class Bread {
161 | constructor(breadType) {
162 | this.breadType = breadType;
163 | //some complex, time consuming operation
164 | console.log("Bread " + breadType + " created.");
165 | }
166 | }
167 | var bakery = new Bakery();
168 | bakery.orderBreadType("Brioche");
169 | bakery.orderBreadType("Anadama bread");
170 | bakery.orderBreadType("Chapati");
171 | bakery.orderBreadType("Focaccia");
172 | console.log(bakery.pickUpBread("Brioche").breadType);
173 | ```
174 |
175 |
--------------------------------------------------------------------------------
/06_함수형_프로그래밍/READMD.md:
--------------------------------------------------------------------------------
1 | # 6. 함수형 프로그래밍
2 |
--------------------------------------------------------------------------------
/07_모델_뷰_패턴/ARUSANTIMO.MD:
--------------------------------------------------------------------------------
1 | MVVM 패턴의 예제라고 생각함
2 |
3 | ```jsx
4 | ### Store
5 | import { observable, action } from 'mobx';
6 |
7 | class Store {
8 | @observable number = 0;
9 |
10 | @action('Store :: setStore')
11 | setStore(key, val) {
12 | this[key] = val;
13 | }
14 |
15 | @action('Store :: setNum')
16 | setNum(num) {
17 | this.number = num;
18 | }
19 | }
20 |
21 | ### provider Store
22 | import Base from './Store';
23 |
24 | import { Provider as MobxProvider } from 'mobx-react';
25 | export default {
26 | return (
27 |
28 |
29 |
30 | );
31 | }
32 |
33 | ###
34 | import * as React from 'react';
35 | import { observer, inject } from 'mobx-react';
36 |
37 | @inject
38 | @observer
39 | export default class View extends React.Component {
40 | render() {
41 | return (
42 |
43 | {this.props.store.number}
44 | this.props.store.setNum(e.target.value)) />
45 |
46 | );
47 | }
48 | }
49 |
50 | ```
51 |
--------------------------------------------------------------------------------
/07_모델_뷰_패턴/BHKIM.md:
--------------------------------------------------------------------------------
1 | # 7. 모델 뷰 패턴
2 |
3 | 1. 모델 뷰 패턴의 역사
4 | 2. MVC 패턴
5 | 3. MVP 패턴
6 | 4. MVVM 패턴
7 |
8 | ### MVC 패턴 (Model View Controller)
9 | ```js
10 | class Model {
11 | constructor(name, description, outerWallThickness, numberOfTowers, moat) {
12 | this.name = name;
13 | this.description = description;
14 | this.outerWallThickness = outerWallThickness;
15 | this.numberOfTowers = numberOfTowers;
16 | this.moat = moat;
17 | }
18 | }
19 | class ValidationResult {
20 | constructor() {
21 | this.Errors = new Array();
22 | }
23 | }
24 | class CreateCastleView {
25 | constructor(document, controller, model, validationResult) {
26 | this.document = document;
27 | this.controller = controller;
28 | this.model = model;
29 | this.validationResult = validationResult;
30 | this.document.getElementById("saveButton").addEventListener("click", () => this.saveCastle());
31 | this.document.getElementById("castleName").value = model.name;
32 | this.document.getElementById("description").value = model.description;
33 | this.document.getElementById("outerWallThickness").value = model.outerWallThickness;
34 | this.document.getElementById("numberOfTowers").value = model.numberOfTowers;
35 | this.document.getElementById("moat").value = model.moat;
36 | }
37 | saveCastle() {
38 | var data = {
39 | name: this.document.getElementById("castleName").value,
40 | description: this.document.getElementById("description").value,
41 | outerWallThickness: this.document.getElementById("outerWallThickness").value,
42 | numberOfTowers: this.document.getElementById("numberOfTowers").value,
43 | moat: this.document.getElementById("moat").value
44 | };
45 | this.controller.saveCastle(data);
46 | }
47 | }
48 | class CreateCastleSuccess {
49 | constructor(document, controller, model) {
50 | this.document = document;
51 | this.controller = controller;
52 | this.model = model;
53 | }
54 | }
55 | class Controller {
56 | constructor(document) {
57 | this.document = document;
58 | }
59 | createCastle() {
60 | this.setView(new CreateCastleView(this.document, this));
61 | }
62 | saveCastle(data) {
63 | var validationResult = this.validate(data);
64 | if (validationResult.IsValid) {
65 | //save castle to storage
66 | this.saveCastleSuccess(data);
67 | }
68 | else {
69 | this.setView(new CreateCastleView(this.document, this, data, validationResult));
70 | }
71 | }
72 | saveCastleSuccess(data) {
73 | this.setView(new CreateCastleSuccess(this.document, this, data));
74 | }
75 | setView(view) {
76 | //send the view to the browser
77 | }
78 | validate(model) {
79 | var validationResult = new validationResult();
80 | if (!model.name || model.name === "") {
81 | validationResult.IsValid = false;
82 | validationResult.Errors.push("Name is required");
83 | }
84 | return;
85 | }
86 | }
87 | ```
88 |
89 | ### MVP 패턴 (Model View Presenter)
90 | ```js
91 | class CreateCastleModel {
92 | constructor(name, description, outerWallThickness, numberOfTowers, moat) {
93 | this.name = name;
94 | this.description = description;
95 | this.outerWallThickness = outerWallThickness;
96 | this.numberOfTowers = numberOfTowers;
97 | this.moat = moat;
98 | }
99 | }
100 | class ValidationResult {
101 | constructor() {
102 | this.Errors = new Array();
103 | }
104 | }
105 | class CreateCastleView {
106 | constructor(document, presenter) {
107 | this.document = document;
108 | this.presenter = presenter;
109 | this.document.getElementById("saveButton").addEventListener("click", this.saveCastle);
110 | }
111 | setCastleName(name) {
112 | this.document.getElementById("castleName").value = name;
113 | }
114 | getCastleName() {
115 | return this.document.getElementById("castleName").value;
116 | }
117 | setDescription(description) {
118 | this.document.getElementById("description").value = description;
119 | }
120 | getDescription() {
121 | return this.document.getElementById("description").value;
122 | }
123 | setOuterWallThickness(outerWallThickness) {
124 | this.document.getElementById("outerWallThickness").value = outerWallThickness;
125 | }
126 | getOuterWallThickness() {
127 | return this.document.getElementById("outerWallThickness").value;
128 | }
129 | setNumberOfTowers(numberOfTowers) {
130 | this.document.getElementById("numberOfTowers").value = numberOfTowers;
131 | }
132 | getNumberOfTowers() {
133 | return parseInt(this.document.getElementById("numberOfTowers").value);
134 | }
135 | setMoat(moat) {
136 | this.document.getElementById("moat").value = moat;
137 | }
138 | getMoat() {
139 | return this.document.getElementById("moat").value;
140 | }
141 | setValid(validationResult) {
142 | }
143 | saveCastle() {
144 | this.presenter.saveCastle();
145 | }
146 | }
147 | class CreateCastlePresenter {
148 | constructor(document) {
149 | this.document = document;
150 | this.model = new CreateCastleModel();
151 | this.view = new CreateCastleView(document, this);
152 | }
153 | saveCastle() {
154 | var data = {
155 | name: this.view.getCastleName(),
156 | description: this.view.getDescription(),
157 | outerWallThickness: this.view.getOuterWallThickness(),
158 | numberOfTowers: this.view.getNumberOfTowers(),
159 | moat: this.view.getMoat()
160 | };
161 | var validationResult = this.validate(data);
162 | if (validationResult.IsValid) {
163 | //write to the model
164 | this.saveCastleSuccess(data);
165 | }
166 | else {
167 | this.view.setValid(validationResult);
168 | }
169 | }
170 | saveCastleSuccess(data) {
171 | //redirect to different presenter
172 | }
173 | validate(model) {
174 | var validationResult = new validationResult();
175 | if (!model.name || model.name === "") {
176 | validationResult.IsValid = false;
177 | validationResult.Errors.push("Name is required");
178 | }
179 | return;
180 | }
181 | }
182 | ```
183 |
184 | ### MVVM 패턴 (Model View ViewModel)
185 | ```js
186 | class CreateCastleModel {
187 | constructor(name, description, outerWallThickness, numberOfTowers, moat) {
188 | this.name = name;
189 | this.description = description;
190 | this.outerWallThickness = outerWallThickness;
191 | this.numberOfTowers = numberOfTowers;
192 | this.moat = moat;
193 | }
194 | }
195 | class ValidationResult {
196 | constructor() {
197 | this.Errors = new Array();
198 | }
199 | }
200 | class CreateCastleView {
201 | constructor(document, viewModel) {
202 | this.document = document;
203 | this.viewModel = viewModel;
204 | this.document.getElementById("saveButton").addEventListener("click", () => this.saveCastle());
205 | this.document.getElementById("name").addEventListener("change", this.nameChangedInView);
206 | this.document.getElementById("description").addEventListener("change", this.descriptionChangedInView);
207 | this.document.getElementById("outerWallThickness").addEventListener("change", this.outerWallThicknessChangedInView);
208 | this.document.getElementById("numberOfTowers").addEventListener("change", this.numberOfTowersChangedInView);
209 | this.document.getElementById("moat").addEventListener("change", this.moatChangedInView);
210 | }
211 | nameChangedInView(name) {
212 | this.viewModel.nameChangedInView(name);
213 | }
214 | nameChangedInViewModel(name) {
215 | this.document.getElementById("name").value = name;
216 | }
217 | descriptionChangedInView(description) {
218 | this.viewModel.descriptionChangedInView(description);
219 | }
220 | descriptionChangedInViewModel(description) {
221 | this.document.getElementById("description").value = description;
222 | }
223 | numberOfTowersChangedInView(numberOfTowers) {
224 | this.viewModel.numberOfTowersChangedInView(numberOfTowers);
225 | }
226 | numberOfTowersChangedInViewModel(numberOfTowers) {
227 | this.document.getElementById("numberOfTowers").value = numberOfTowers;
228 | }
229 | outerWallThicknessChangedInView(outerWallThickness) {
230 | this.viewModel.outerWallThicknessChangedInView(outerWallThickness);
231 | }
232 | outerWallThicknessChangedInViewModel(outerWallThickness) {
233 | this.document.getElementById("outerWallThickness").value = outerWallThickness;
234 | }
235 | moatChangedInView(moat) {
236 | this.viewModel.moatChangedInView(moat);
237 | }
238 | moatChangedInViewModel(moat) {
239 | this.document.getElementById("moat").value = moat;
240 | }
241 | isValidChangedInViewModel(validationResult) {
242 | this.document.getElementById("validationWarning").innerHtml = validationResult.Errors;
243 | this.document.getElementById("validationWarning").className = "visible";
244 | }
245 | saveCastle() {
246 | this.viewModel.saveCastle();
247 | }
248 | }
249 | class CreateCastleViewModel {
250 | constructor(document) {
251 | this.document = document;
252 | this.model = new CreateCastleModel();
253 | this.view = new CreateCastleView(document, this);
254 | }
255 | nameChangedInView(name) {
256 | this.name = name;
257 | }
258 | nameChangedInViewModel(name) {
259 | this.view.nameChangedInViewModel(name);
260 | }
261 | descriptionChangedInView(description) {
262 | this.description = description;
263 | }
264 | descriptionChangedInViewModel(description) {
265 | this.view.descriptionChangedInViewModel(description);
266 | }
267 | outerWallThicknessChangedInView(outerWallThickness) {
268 | this.outerWallThickness = outerWallThickness;
269 | }
270 | outerWallThicknessChangedInViewModel(outerWallThickness) {
271 | this.view.outerWallThicknessChangedInViewModel(outerWallThickness);
272 | }
273 | numberOfTowersChangedInView(numberOfTowers) {
274 | this.numberOfTowers = numberOfTowers;
275 | }
276 | numberOfTowersChangedInViewModel(numberOfTowers) {
277 | this.view.numberOfTowersChangedInViewModel(numberOfTowers);
278 | }
279 | moatChangedInView(moat) {
280 | this.moat = moat;
281 | }
282 | moatChangedInViewModel(moat) {
283 | this.view.moatChangedInViewModel(moat);
284 | }
285 | saveCastle() {
286 | var validationResult = this.validate();
287 | if (validationResult.IsValid) {
288 | //write to the model
289 | this.saveCastleSuccess();
290 | }
291 | else {
292 | this.view.isValidChangedInViewModel(validationResult);
293 | }
294 | }
295 | saveCastleSuccess() {
296 | //do whatever is needed when save is successful.
297 | //Possibly update the view model
298 | }
299 | validate() {
300 | var validationResult = new validationResult();
301 | if (!this.name || this.name === "") {
302 | validationResult.IsValid = false;
303 | validationResult.Errors.push("Name is required");
304 | }
305 | return;
306 | }
307 | }
308 | ```
309 |
310 | ### MVC vs MVP vs MVVM
311 | 
312 | 
313 | Ref. [MVC, MVP and MVVM Design Patterns](https://www.slideshare.net/mudasirqazi00/design-patterns-mvc-mvp-and-mvvm)
314 |
--------------------------------------------------------------------------------
/07_모델_뷰_패턴/DOHYUNG.md:
--------------------------------------------------------------------------------
1 | # 7장 (Model View Patterns)
2 |
3 | - 애플리케이션의 계층를 나누는 것(Layering)과 모듈화를 유지하는 것은 변화에 의한 충격을 줄일 수 있다. 각 계층은 서로를 모를 수록 좋다. 계층 사이에 간단한 인터페이스를 유지하여 한 레이어가 변할 때 다른 레이어로 문제가 침투하는 것을 막을 수 있다.
4 |
5 | - MVC(Model View Controller)
6 | - 이상적으로 모델이 이 패턴에서 유일하게 변화가 가능한 부분이다. 뷰나 컨트롤러는 상태에 직접적으로 상태를 가지고 있어서는 안된다. 또한 모델은 뷰와 컨트롤러의 존재를 모르게 만든다.
7 | - [Naked Object pattern](https://en.wikipedia.org/wiki/Naked_objects) - 모델이 비지니스 로직을 들고 있고, 뷰와 컨트롤러는 그 모델에 의해 자동으로 생성된 계층이 되도록 하는 패턴
8 | - 컨트롤러는 보통 뷰와 모델의 존재를 알고 있고 그 둘을 가운데서 조작하는 역할을 한다. 필요한 상황에 따라 알맞은 뷰를 선택하고 그 뷰와 모델 사이에 통신을 담당한다.
9 | - PAC(Presentation-Abstraction-Control) - Abstraction 부분이 모델의 역할을 일부 담당하는데, 전체 모델을 보이기 보다 일부 필드만 담당하는 방식으로 되어있다. 이를 활용하면 컴포넌트의 병렬 처리가 가능하다고는 하는데...?
10 | - Naked Object pattern같이 모델에 따라 동적으로 뷰와 컨트롤러를 생성하는 구조가 아니라면 특정 데이터 필드가 모든 계층에 걸쳐 있는 것이 그리 신경 쓸 일은 아니다. 종단 관심사는 보통 데이터 접속 및 로깅 등을 일컫기 때문이다.
11 | - 구현 예 - [MVC Pattern Example - JSFiddle](https://jsfiddle.net/rinae/thxf6o06/20/)
12 |
13 | ```javascript
14 | function LoginController(model) {
15 | return Object.freeze({
16 | login,
17 | checkPassword
18 | });
19 |
20 | function login({ userName, password, rememberMe }) {
21 | if (checkPassword(userName, password)) {
22 | model.LoginSuccessful = false;
23 | model.LoginErrorMessage = "Incorrect username or password";
24 | } else {
25 | model.UserName = userName;
26 | model.Password = password;
27 | model.RememberMe = rememberMe;
28 | model.LoginSuccessful = true;
29 | }
30 |
31 | return model;
32 | }
33 |
34 | function checkPassword(userName, password) {
35 | return userName === password;
36 | }
37 | }
38 |
39 | var userModel = {
40 | UserName: '',
41 | Password: '',
42 | RememberMe: false,
43 | LoginSuccessful: false,
44 | LoginErrorMessage: ''
45 | };
46 |
47 | var lct = LoginController(userModel);
48 |
49 | lct.login({
50 | userName: 'John',
51 | password: 'Wick',
52 | rememberMe: true
53 | });
54 | console.info(userModel);
55 | ```
56 |
57 |
58 | - MVP
59 | - Presenter 는 하나의 뷰와 연결되어있다. 컨트롤러가 어떤 뷰를 선택할지를 고르는 로직이 없어진다. 아니면 이 패턴의 범주를 벗어나는 상위 개념으로 존재하게 된다. 적절한 프레젠터를 고르는 일은 라우터에 따라 결정될 수도 있다.
60 | - 프레젠터는 뷰와 모델을 알지만, 뷰와 모델은 서로를 모른다. 모든 작업은 프레젠터를 통해 관리된다.
61 | - 뷰와 모델이 분리될 수록 중간에서 둘을 관리하는 엄격한 API를 만들어서 변화를 잘 받아들이게 할 수 있다. 하지만 더 많은 코드가 발생할 수 있는데, 이는 더 많은 버그가 생길 가능성을 의미한다.
62 | - [MVP Pattern Example - JSFiddle](https://jsfiddle.net/rinae/8kbtx8cm/4/)
63 | - MVVM
64 | - Presetner 대신에 ViewModel이 자리잡게 되는데, 이 뷰 모델은 Presenter처럼 메서드 호출로 값을 변경하는게 아니라 직접적인 값 변경에 즉각적, 혹은 약간 지연하여 모델에 변경 상태를 저장한다. 보통은 `save` 같은 동작으로 모델에 변경 데이터를 새로 전달할 수 있다.
65 | - 이 패턴에서 모델은 단순한 데이터 저장 객체이다. 또한 뷰의 변경을 바로 뷰 모델로 반영하는 것 뿐 아니라 뷰 모델의 변경도 뷰에 반영된다.
66 | - 한 뷰는 여러 개의 뷰 모델을 가질 수 있다. 따라서 서로 다른 뷰 모델이 영향을 주고 받으면서 그에 연결되어 있는 뷰도 바뀔 수 있다.
67 | - [MVVM Pattern Example - JSFiddle](https://jsfiddle.net/rinae/jk4cvm44/6/)
--------------------------------------------------------------------------------
/07_모델_뷰_패턴/JieunPark.md:
--------------------------------------------------------------------------------
1 | ## 1. MVVM 모델
2 |
3 | : MVVM 패턴의 ViewModel 레이어에 해당하는 View 라이브러리
4 | 
5 |
6 | ## 2\. MVVM 구조
7 |
8 | MV에, `C(Controller)가 아닌 VM(ViewModel)`입니다.
9 | 
10 | 구조상으로 보면, 모델과 뷰 사이에 위치하고 있습니다.
11 | 모델로부터 어떤 데이터를 가져오는데, 그 데이터는 뷰에 적한한 형태의 데이터로 가공됩니다.
12 | 그래서 뷰 모델이 변경될때마다 자동으로 그것과 연될되어있는 뷰화면에 반영이 되죠.
13 | 그렇기때문에 모델보다는 좀 더 뷰에 적합한 모델이라고 보시면 됩니다. 그래서 이름도 `ViewModel`이라고 한 것 같습니다.
14 | 이렇게 Vue.js는 ViewModel을 이용하여 뷰모델이 변경됨에 따라서, 자동으로 화면에 갱신되어지고 있습니다.
15 |
16 | ## 3\. `ViewModel`을 직접 체험해보기
17 |
18 | : 어떻게 뷰모델이 변경됨에 따라서, 자동으로 화면에 갱신이 되는지 확인해보겠습니다.
19 |
20 | ### 3.1. Chrome으로 ViewModel 만들기
21 |
22 | : 아래 소스를 chrome console에 copy 하신 후 실행해보시면, 아래와 같이 확인해 보실 수 있습니다.
23 |
24 | ```javascript
25 | const h1 = document.createElement('h1');
26 | document.body.appendChild(h1);
27 | const viewModel = {};
28 | let model = ''
29 |
30 | //make ViewModel
31 | Object.defineProperty(viewModel,'model', {
32 | 'get' : function(){ return model; },
33 | 'set' : function(val) {
34 | model = val;
35 | h1.innerHTML = model;
36 | }
37 | });
38 |
39 | //check ViewModel
40 | viewModel.model = 'Hi ViewModel!'
41 | ```
42 |
43 | 
44 |
45 | ### 3.2. ViewModel 값 입력 후, 출력 내용 확인 해보기 1
46 |
47 | : 이러면, 뷰모델이 변경됨에 따라서, `자동으로 화면에 'Hi NHN godo!'가 갱신` 되는것을 볼 수 있습니다.
48 |
49 | ```javascript
50 | viewModel.model = 'Hi ViewModel!'
51 | >> "Hi ViewModel!"
52 |
53 | viewModel.model = 'merong?!'
54 |
55 | >>>
56 | "merong"
57 |
58 | ```
59 | 
--------------------------------------------------------------------------------
/07_모델_뷰_패턴/READMD.md:
--------------------------------------------------------------------------------
1 | # 7. 모델 뷰 패턴
2 |
--------------------------------------------------------------------------------
/07_모델_뷰_패턴/bhkim_/img1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/js-jsm/pattern2018/7cf9926ba023abcb1c5ceec91add5ac4407bc2dc/07_모델_뷰_패턴/bhkim_/img1.png
--------------------------------------------------------------------------------
/07_모델_뷰_패턴/bhkim_/img2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/js-jsm/pattern2018/7cf9926ba023abcb1c5ceec91add5ac4407bc2dc/07_모델_뷰_패턴/bhkim_/img2.png
--------------------------------------------------------------------------------
/07_모델_뷰_패턴/jaenam.md:
--------------------------------------------------------------------------------
1 | # CH7. MV* Patterns
2 |
3 | ## 1. MVC
4 |
5 | ```js
6 | class Model {
7 | constructor (name = '없음', number = 0) {
8 | this.name = name;
9 | this.number = number;
10 | }
11 | }
12 | class View {
13 | printDetails (model) {
14 | console.log(`name: ${model.name}, number: ${model.number}`);
15 | }
16 | }
17 | class Controller {
18 | constructor (view, model) {
19 | this.view = view;
20 | this.model = model;
21 | }
22 | get name () {
23 | return this.model.name;
24 | }
25 | set name (name) {
26 | this.model.name = name;
27 | }
28 | get number () {
29 | return this.model.number;
30 | }
31 | set number (number) {
32 | this.model.number = number;
33 | }
34 | updateView () {
35 | this.view.printDetails(this.model);
36 | }
37 | }
38 | const view = new View();
39 | const model = new Model();
40 | const ctrl = new Controller(view, model);
41 | ctrl.updateView();
42 |
43 | ctrl.name = '재남';
44 | ctrl.number = 5;
45 | ctrl.updateView();
46 | ```
47 |
48 | ## 2. MVP
49 |
50 | ```js
51 | class Presenter {
52 | constructor (view) {
53 | this.view = view
54 | view.setPresenter(this)
55 | }
56 | getWeather (latitude, longitude) {
57 | const model = new Model(this);
58 | model.getWeather(latitude, longitude)
59 | }
60 | callback (weather) {
61 | this.view.showWeather(weather)
62 | }
63 | }
64 | class Model {
65 | constructor (cb) {
66 | this.weatherCallback = cb
67 | }
68 | getWeather (latitude, longitude) {
69 | this.weatherCallback.callback(`위도 ${latitude}, 경도 ${longitude}의 날씨는 모릅니다.`)
70 | }
71 | }
72 | class View {
73 | setPresenter (pres) {
74 | this.presenter = pres
75 | }
76 | showWeather (weather) {
77 | console.log(weather)
78 | }
79 | }
80 | const pres = new Presenter(new View())
81 | pres.getWeather(10, 20)
82 | ```
83 |
84 |
85 | ## 3. MVVM
86 |
87 |
--------------------------------------------------------------------------------
/07_모델_뷰_패턴/jieunpark_/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/js-jsm/pattern2018/7cf9926ba023abcb1c5ceec91add5ac4407bc2dc/07_모델_뷰_패턴/jieunpark_/1.png
--------------------------------------------------------------------------------
/07_모델_뷰_패턴/jieunpark_/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/js-jsm/pattern2018/7cf9926ba023abcb1c5ceec91add5ac4407bc2dc/07_모델_뷰_패턴/jieunpark_/2.png
--------------------------------------------------------------------------------
/07_모델_뷰_패턴/jieunpark_/3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/js-jsm/pattern2018/7cf9926ba023abcb1c5ceec91add5ac4407bc2dc/07_모델_뷰_패턴/jieunpark_/3.gif
--------------------------------------------------------------------------------
/07_모델_뷰_패턴/jieunpark_/4.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/js-jsm/pattern2018/7cf9926ba023abcb1c5ceec91add5ac4407bc2dc/07_모델_뷰_패턴/jieunpark_/4.gif
--------------------------------------------------------------------------------
/07_모델_뷰_패턴/jinho.md:
--------------------------------------------------------------------------------
1 | # 7. 모델 뷰 패턴 - 현진호
2 |
3 | ### MVC, MVP, MVVP 패턴의 도입 이유
4 | - **화면에 보여주는 로직과 실제 데이터가 처리 되는 로직을 분리**
5 | - 각 계층을 분리시킴으로써 코드의 재활용성을 높이고 불필요한 중복을 막을 수 있다.
6 | - Model과 VIew의 의존성을 어떻게 제어하느냐에 따라 각 패턴을 분류할 수 있다.
7 |
8 | ### Model
9 | - 프로그램에서 사용되는 실제 데이터 및 데이터 조작 로직을 처리하는 부분
10 | - 비즈니스 규칙, 유효성 검사 논리, 그 밖의 다양한 기능을 포함할 수도 있다.
11 |
12 | ### View
13 | - 사용자에게 제공되어 보여지는 UI 부분
14 | - 다수의 뷰는 같은 모델 데이터를 다양하게 표시할 수 있다
15 |
16 |
17 |
18 |
19 | ### MVC Model-View-Controller
20 | - Controller
21 | - 사용자의 입력을 받고 처리하는 부분
22 | - 애플리케이션의 이벤트를 리스닝해 모델과 뷰 사이의 명령을 위임
23 | - 처리 과정
24 | 1. Controller로 사용자의 입력이 들어옴
25 | 2. Controller는 Model을 이용해 데이터 업데이트 / 로드, Model을 표시할 View를 선택
26 | - 하나의 Controller가 여러개의 View를 선택하여 Model을 나타내어 줄 수 있다
27 | - 이때 Controller는 View를 선택만하고 업데이트를 시켜주지 않기 때문에 View는 Model을 이용하여 업데이트
28 | - Model을 직접 사용하거나 Model에서 View에게 Notify해주는 방법, View에서 Polling을 통해 Model의 변화를 알아채는 방법 등이 있다
29 | 3. Model은 해당 데이터를 보여줄 View를 선택해서 화면에 표시
30 |
31 | - 단점
32 | - View와 Model이 서로 의존적
33 | - View는 Model을 이용하기 때문에 서로간의 의존성을 완벽히 피할 수 없다
34 | - 컨트롤러가 다수의 뷰에 걸친 이벤트를 리스닝해서 종속성이 강함
35 | - 단위 테스트 어려움
36 | - 좋은 MVC 패턴은 View와 Model간의 의존성을 최대한 적게 한 패턴
37 |
38 | ### MVP Model-View-Presenter
39 | - Presenter
40 | - View에서 요청한 정보를 Model로 부터 가공해서 View로 전달하는 부분
41 | - Presenter는 View의 인스턴스를 갖고 있으며 View와 1대1 관계. 그에 해당하는 Model의 인스턴스 또한 갖고 있기때문에 View와 Model 사이에서 다리와 같은 역할을 함
42 |
43 | - 절차
44 | 1. View로 사용자의 입력이 들어옴
45 | 2. View는 Presenter에 작업 요청
46 | 3. Presenter에서 필요한 데이터를 Model에 요청
47 | 4. Model은 Presenter에 필요한 데이터를 응답
48 | 5. Presenter는 View에 데이터를 전달
49 | 6. View는 Presenter로부터 받은 데이터로 화면에 표시
50 |
51 | - MVC와의 차이점
52 | - MVC에서 컨트롤러가 Presenter로 교체된 형태이고 프리젠터는 뷰와 같은 레벨에 있음
53 | - Model과 View는 MVC와 동일하지만 사용자 입력을 View에서 받음
54 | - Model과 View는 각각 Presenter와 상호 동작
55 | - 항상 Presenter을 거쳐서 동작
56 | - MVC의 단점인 Model과 View의 의존성 문제 해결
57 | - View와 Model은 서로를 알필요가 전혀 없고, Presenter만 알면 됨
58 | - MVC 패턴과는 다르게 Presenter를 통해 Model과 View를 완벽히 분리해 줌
59 |
60 | - 단점
61 | - View와 Presenter가 1:1로 강한 의존성을 가짐
62 |
63 | ### MVVM
64 | - ViewModel
65 | - View를 표현하기 위해 만들어진, View를 위한 Model
66 | - 모델의 데이터와 뷰의 데이터 표시 사이의 변화(예: 데이터 바인딩)를 관리
67 | - 뷰를 직접 조작하는 애플리케이션 논리가 최소화되거나 아예 사라져 모델과 프레임워크가 최대한 많은 작업이 가능
68 | - 여러 View들은 하나의 ViewModel을 선택하여 바인딩하고 업데이트를 받음
69 | - 뷰모델은 뷰가 필요로 하든 데이터와 커맨드 객체를 노출해 주기 때문에 뷰가 필요로하는 데이터와 액션은 담고 있는 컨테이너 객체로 볼 수도 있음
70 |
71 | - 디자인 패턴을 사용
72 | - Command 패턴, Data Binding 패턴(Observer 패턴)
73 | - View와 ViewModel은 의존성이 완전히 사라짐
74 |
75 | - 절차
76 | - View에 입력이 들어오면 Command 패턴으로 ViewModel에 명령
77 | - ViewModel은 필요한 데이터를 Model에 요청
78 | - Model은 ViewModel에 필요한 데이터를 응답
79 | - ViewModel은 응답 받은 데이터를 가공해서 저장
80 | - View는 ViewModel과의 Data Binding으로 인해 자동으로 갱
81 |
82 |
83 | ### Reference
84 | - [hashcode - MVC, MVP, MVVM의 차이를 자세하게 알고 싶습니다.](http://hashcode.co.kr/questions/3764/mvc-mvp-mvvm의-차이를-자세하게-알고-싶습니다)
85 | - [Outsider's Dev Story - MVVM 패턴에 대해서...](https://blog.outsider.ne.kr/672)
86 | - [공대인들이 직접쓰는 컴퓨터공부방 - [WPF] MVC, MVP, MVVM 차이점](http://hackersstudy.tistory.com/71)
87 | - [마기의 개발 블로그 - MVC, MVP, MVVM 비교](https://magi82.github.io/android-mvc-mvp-mvvm/)
88 | - [프론트엔드 웹애플리케이션 아키텍쳐 비교분석 : MVC와 MVVM](https://medium.com/@manyoung/프론트엔드-웹애플리케이션-아키텍쳐-비교분석-mvc와-mvvm-e446a0f46d8c)
89 |
--------------------------------------------------------------------------------
/07_모델_뷰_패턴/jinho_/img/MVC-MVP-MVVM.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/js-jsm/pattern2018/7cf9926ba023abcb1c5ceec91add5ac4407bc2dc/07_모델_뷰_패턴/jinho_/img/MVC-MVP-MVVM.png
--------------------------------------------------------------------------------
/07_모델_뷰_패턴/jinho_/mvc/EventDispatcher.js:
--------------------------------------------------------------------------------
1 | var Event = function (sender) {
2 | this._sender = sender;
3 | this._listeners = [];
4 | };
5 |
6 | Event.prototype = {
7 | attach: function (listener) {
8 | this._listeners.push(listener);
9 | },
10 |
11 | notify: function (args) {
12 | for (var i = 0; i < this._listeners.length; i += 1) {
13 | this._listeners[i](this._sender, args);
14 | }
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/07_모델_뷰_패턴/jinho_/mvc/TaskController.js:
--------------------------------------------------------------------------------
1 | var TaskController = function (model, view) {
2 | this.model = model;
3 | this.view = view;
4 |
5 | this.init();
6 | };
7 |
8 | TaskController.prototype = {
9 | init() {
10 | this.view.addTaskEvent.attach(this.addTask.bind(this));
11 | this.view.completeTaskEvent.attach(this.completeTask.bind(this));
12 | this.view.deleteTaskEvent.attach(this.deleteTask.bind(this));
13 | this.view.selectTaskEvent.attach(this.selectTask.bind(this));
14 | this.view.unselectTaskEvent.attach(this.unselectTask.bind(this));
15 | },
16 |
17 | addTask(sender, args) {
18 | this.model.addTask(args.task);
19 | },
20 |
21 | selectTask(sender, args) {
22 | this.model.setSelectedTask(args.taskIndex);
23 | },
24 |
25 | unselectTask(sender, args) {
26 | this.model.unselectTask(args.taskIndex);
27 | },
28 |
29 | completeTask() {
30 | this.model.setTasksAsCompleted();
31 | },
32 |
33 | deleteTask() {
34 | this.model.deleteTasks();
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/07_모델_뷰_패턴/jinho_/mvc/TaskModel.js:
--------------------------------------------------------------------------------
1 | var TaskModel = function () {
2 | this.tasks = [];
3 | this.selectedTasks = [];
4 | this.addTaskEvent = new Event(this);
5 | this.removeTaskEvent = new Event(this);
6 | this.setTasksAsCompletedEvent = new Event(this);
7 | this.deleteTasksEvent = new Event(this);
8 | };
9 |
10 | TaskModel.prototype = {
11 |
12 | addTask (task) {
13 | this.tasks.push({
14 | taskName: task,
15 | taskStatus: 'uncompleted'
16 | });
17 | this.addTaskEvent.notify();
18 | },
19 |
20 | getTasks () {
21 | return this.tasks;
22 | },
23 |
24 | setSelectedTask (taskIndex) {
25 | this.selectedTasks.push(taskIndex);
26 | },
27 |
28 | unselectTask (taskIndex) {
29 | this.selectedTasks.splice(taskIndex, 1);
30 | },
31 |
32 | setTasksAsCompleted () {
33 | var selectedTasks = this.selectedTasks;
34 | for (var index in selectedTasks) {
35 | this.tasks[selectedTasks[index]].taskStatus = 'completed';
36 | }
37 |
38 | this.setTasksAsCompletedEvent.notify();
39 | this.initializeSelectedTasks();
40 | },
41 |
42 | deleteTasks () {
43 | var selectedTasks = this.selectedTasks.sort();
44 |
45 | for (var i = selectedTasks.length - 1; i >= 0; i--) {
46 | this.tasks.splice(this.selectedTasks[i], 1);
47 | }
48 |
49 | this.deleteTasksEvent.notify();
50 | this.initializeSelectedTasks();
51 | },
52 |
53 | initializeSelectedTasks() {
54 | this.selectedTasks = [];
55 | }
56 | };
57 |
--------------------------------------------------------------------------------
/07_모델_뷰_패턴/jinho_/mvc/TaskView.js:
--------------------------------------------------------------------------------
1 | var TaskView = function (model) {
2 | this.model = model;
3 | this.addTaskEvent = new Event(this);
4 | this.selectTaskEvent = new Event(this);
5 | this.unselectTaskEvent = new Event(this);
6 | this.completeTaskEvent = new Event(this);
7 | this.deleteTaskEvent = new Event(this);
8 |
9 | this.init();
10 | };
11 |
12 | TaskView.prototype = {
13 |
14 | init() {
15 | this.$container = $('.js-container');
16 | this.$addTaskButton = this.$container.find('.js-add-task-button');
17 | this.$taskTextBox = this.$container.find('.js-task-textbox');
18 | this.$tasksContainer = this.$container.find('.js-tasks-container');
19 |
20 | this.$addTaskButton.click(this.addTaskButton.bind(this));
21 | this.$container.on('click', '.js-task', this.selectOrUnselectTask.bind(this));
22 | this.$container.on('click', '.js-complete-task-button', this.completeTaskButton.bind(this));
23 | this.$container.on('click', '.js-delete-task-button', this.deleteTaskButton.bind(this));
24 |
25 | this.model.addTaskEvent.attach(this.addTask.bind(this));
26 | this.model.addTaskEvent.attach(this.clearTaskTextBox.bind(this));
27 | this.model.setTasksAsCompletedEvent.attach(this.setTasksAsCompleted.bind(this));
28 | this.model.deleteTasksEvent.attach(this.deleteTasks.bind(this));
29 | },
30 |
31 | addTaskButton() {
32 | this.addTaskEvent.notify({
33 | task: this.$taskTextBox.val()
34 | });
35 | },
36 |
37 | completeTaskButton() {
38 | this.completeTaskEvent.notify();
39 | },
40 |
41 | deleteTaskButton() {
42 | this.deleteTaskEvent.notify();
43 | },
44 |
45 | selectOrUnselectTask() {
46 | var taskIndex = $(event.target).attr("data-index");
47 |
48 | if ($(event.target).attr('data-task-selected') == 'false') {
49 | $(event.target).attr('data-task-selected', true);
50 | this.selectTaskEvent.notify({
51 | taskIndex: taskIndex
52 | });
53 | } else {
54 | $(event.target).attr('data-task-selected', false);
55 | this.unselectTaskEvent.notify({
56 | taskIndex: taskIndex
57 | });
58 | }
59 | },
60 |
61 | buildList() {
62 | var tasks = this.model.getTasks();
63 | var html = "";
64 | var $tasksContainer = this.$tasksContainer;
65 |
66 | $tasksContainer.html('');
67 |
68 | var index = 0;
69 | for (var task in tasks) {
70 | if (tasks[task].taskStatus == 'completed') {
71 | html += '';
72 | } else {
73 | html += "
";
74 | }
75 | $tasksContainer.append(`${html} ${tasks[task].taskName}
`);
76 | index++;
77 | }
78 | },
79 |
80 | clearTaskTextBox() {
81 | this.$taskTextBox.val('');
82 | },
83 |
84 | addTask() {
85 | this.buildList();
86 | },
87 |
88 | setTasksAsCompleted() {
89 | this.buildList();
90 | },
91 |
92 | deleteTasks() {
93 | this.buildList();
94 | }
95 | };
96 |
--------------------------------------------------------------------------------
/07_모델_뷰_패턴/jinho_/mvc/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
19 |
20 |
21 |
22 |
23 |
24 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/07_모델_뷰_패턴/wonjun.md:
--------------------------------------------------------------------------------
1 | 모델뷰 패턴들은 애플리케이션 레벨에서 적용할수 있는 큰 규모의 패턴들 이다.
2 |
3 | 먼저 소프트웨어 에서 디자인 패턴이란 무엇일까? 위키에서 정의를 찾아 보았다.
4 |
5 | >소프트웨어 개발 방법에서 사용되는 디자인 패턴은, 프로그램 개발에서 자주 나타나는 과제를 해결하기 위한 방법 중 하나로, 과거의 소프트웨어 개발 과정에서 발견된 설계의 노하우를 축적하여 이름을 붙여, 이후에 재이용하기 좋은 형태로 특정의 규약을 묶어서 정리한 것이다. 알고리즘과 같이 프로그램 코드로 바로 변환될 수 있는 형태는 아니지만, 특정한 상황에서 구조적인 문제를 해결하는 방식을 설명해 준다.
6 |
7 | 패턴은 전체 어플리케이션이 어떻게 조합 되는지 가이드를 제공해 줄수 있다.
8 |
9 | 그렇다면, 디자인 패턴은 왜 중요한것일까?
10 |
11 | 애플리케이션에서 문제의 분리는 매우 중요하다.
12 |
13 | 애플리케이션의 계층을 나누고 모듈화를 유지하면 변화의 영향을 최소화 시킬수 있다.
14 |
15 | 변화의 영향을 최소화 한다는것은 작업량이 줄어듦을 의미하면서, 문제가 발생 되었을때 그 확산을 최소화 할수 있다는 점에서 매우 유용하다고 볼수 있을것이다.
16 |
17 | ## MVC 패턴
18 |
19 | MVC 패턴의 특징은 컨트롤러가 중심이라는 점이다.
20 | 컨트롤러는 애플리케이션의 실제 기능을 포함하며, 뷰를 정확하게 선택하고 모델과 뷰사이의 통신을 연결하는 책임을 가진다.
21 | MVC 패턴의은 모든 입력이 컨트롤러에서 처리 된다.
22 | 처리 과정에서 컨트롤러는 모델을 업데이트 하고 뷰를 선택 한다.
23 | MVC 패턴의 문제점은 뷰는 모델을 이용해 업데이트 하기 때문에 의존성이 높다는점이다.
24 |
--------------------------------------------------------------------------------
/07_모델_뷰_패턴/wonjun_/app.js:
--------------------------------------------------------------------------------
1 | var createError = require('http-errors');
2 | var express = require('express');
3 | var path = require('path');
4 | var cookieParser = require('cookie-parser');
5 | var lessMiddleware = require('less-middleware');
6 | var logger = require('morgan');
7 |
8 | var indexRouter = require('./routes/index');
9 |
10 | var app = express();
11 |
12 | // view engine setup
13 | app.set('views', path.join(__dirname, 'views'));
14 | app.set('view engine', 'hbs');
15 |
16 | app.use(logger('dev'));
17 | app.use(express.json());
18 | app.use(express.urlencoded({ extended: false }));
19 | app.use(cookieParser());
20 | app.use(lessMiddleware(path.join(__dirname, 'public')));
21 | app.use(express.static(path.join(__dirname, 'public')));
22 |
23 | app.use('/', indexRouter);
24 |
25 | // catch 404 and forward to error handler
26 | app.use(function(req, res, next) {
27 | next(createError(404));
28 | });
29 |
30 | // error handler
31 | app.use(function(err, req, res, next) {
32 | // set locals, only providing error in development
33 | res.locals.message = err.message;
34 | res.locals.error = req.app.get('env') === 'development' ? err : {};
35 |
36 | // render the error page
37 | res.status(err.status || 500);
38 | res.render('error');
39 | });
40 |
41 | module.exports = app;
42 |
--------------------------------------------------------------------------------
/07_모델_뷰_패턴/wonjun_/bin/www:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * Module dependencies.
5 | */
6 |
7 | var app = require('../app');
8 | var debug = require('debug')('mvc-exam:server');
9 | var http = require('http');
10 |
11 | /**
12 | * Get port from environment and store in Express.
13 | */
14 |
15 | var port = normalizePort(process.env.PORT || '3001');
16 | app.set('port', port);
17 |
18 | /**
19 | * Create HTTP server.
20 | */
21 |
22 | var server = http.createServer(app);
23 |
24 | /**
25 | * Listen on provided port, on all network interfaces.
26 | */
27 |
28 | server.listen(port);
29 | server.on('error', onError);
30 | server.on('listening', onListening);
31 |
32 | /**
33 | * Normalize a port into a number, string, or false.
34 | */
35 |
36 | function normalizePort(val) {
37 | var port = parseInt(val, 10);
38 |
39 | if (isNaN(port)) {
40 | // named pipe
41 | return val;
42 | }
43 |
44 | if (port >= 0) {
45 | // port number
46 | return port;
47 | }
48 |
49 | return false;
50 | }
51 |
52 | /**
53 | * Event listener for HTTP server "error" event.
54 | */
55 |
56 | function onError(error) {
57 | if (error.syscall !== 'listen') {
58 | throw error;
59 | }
60 |
61 | var bind = typeof port === 'string'
62 | ? 'Pipe ' + port
63 | : 'Port ' + port;
64 |
65 | // handle specific listen errors with friendly messages
66 | switch (error.code) {
67 | case 'EACCES':
68 | console.error(bind + ' requires elevated privileges');
69 | process.exit(1);
70 | break;
71 | case 'EADDRINUSE':
72 | console.error(bind + ' is already in use');
73 | process.exit(1);
74 | break;
75 | default:
76 | throw error;
77 | }
78 | }
79 |
80 | /**
81 | * Event listener for HTTP server "listening" event.
82 | */
83 |
84 | function onListening() {
85 | var addr = server.address();
86 | var bind = typeof addr === 'string'
87 | ? 'pipe ' + addr
88 | : 'port ' + addr.port;
89 | debug('Listening on ' + bind);
90 | }
91 |
--------------------------------------------------------------------------------
/07_모델_뷰_패턴/wonjun_/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mvc-exam",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "start": "node ./bin/www"
7 | },
8 | "dependencies": {
9 | "cookie-parser": "~1.4.3",
10 | "debug": "~2.6.9",
11 | "express": "~4.16.0",
12 | "hbs": "~4.0.1",
13 | "http-errors": "~1.6.2",
14 | "less-middleware": "~2.2.1",
15 | "morgan": "~1.9.0"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/07_모델_뷰_패턴/wonjun_/public/javascripts/mvp.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/07_모델_뷰_패턴/wonjun_/public/stylesheets/style.css:
--------------------------------------------------------------------------------
1 | body{padding:50px;font:14px "Lucida Grande",Helvetica,Arial,sans-serif}a{color:#00B7FF}input[type=text]{padding:0 2px;height:25px}button{height:29px;padding:0 20px;top:1px;position:relative}ul{list-style:none}ul li{border:1px solid #00B7FF;padding:10px;margin:2px 0}
--------------------------------------------------------------------------------
/07_모델_뷰_패턴/wonjun_/public/stylesheets/style.less:
--------------------------------------------------------------------------------
1 | body {
2 | padding: 50px;
3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
4 | }
5 |
6 | a {
7 | color: #00B7FF;
8 | }
9 |
10 | input[type=text] {
11 | padding: 0 2px;
12 | height: 25px;
13 | }
14 |
15 | button {
16 | height: 29px;
17 | padding: 0 20px;
18 | top: 1px;
19 | position: relative;
20 | }
21 |
22 | ul {
23 | list-style: none;
24 | li {
25 | border: 1px solid #00B7FF;
26 | padding: 10px;
27 | margin: 2px 0;
28 | }
29 | }
--------------------------------------------------------------------------------
/07_모델_뷰_패턴/wonjun_/routes/index.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var router = express.Router();
3 |
4 | // model
5 | var users = [
6 | { "firstName": "지나", "lastName": "이" },
7 | { "firstName": "도형", "lastName": "안" },
8 | { "firstName": "성훈", "lastName": "백" },
9 | { "firstName": "재남", "lastName": "정" },
10 | { "firstName": "진호", "lastName": "현" },
11 | { "firstName": "병화", "lastName": "김" },
12 | { "firstName": "지은", "lastName": "이" },
13 | { "firstName": "정현", "lastName": "이" },
14 | { "firstName": "창규", "lastName": "이" },
15 | { "firstName": "원준", "lastName": "황" }
16 | ]
17 |
18 | // controllers
19 | router.get('/', function(req, res, next) {
20 | var UsersModel = users.map(function(user) { return Object.assign({}, user)})
21 | // view
22 | res.render('mvc', { title: 'MVC - Study Members', users: UsersModel });
23 | });
24 |
25 | router.post('/user', function(req, res, next) {
26 | users.push({ firstName: req.body.firstName, lastName: req.body.lastName })
27 | // view
28 | res.redirect(301, '/')
29 | })
30 |
31 | module.exports = router;
32 |
--------------------------------------------------------------------------------
/07_모델_뷰_패턴/wonjun_/views/error.hbs:
--------------------------------------------------------------------------------
1 |
{{message}}
2 |
{{error.status}}
3 |
{{error.stack}}
4 |
--------------------------------------------------------------------------------
/07_모델_뷰_패턴/wonjun_/views/index.hbs:
--------------------------------------------------------------------------------
1 |
{{title}}
2 |
7 |
8 | {{#each users}}
9 | {{this.lastName}} {{this.firstName}}
10 | {{/each}}
11 |
--------------------------------------------------------------------------------
/07_모델_뷰_패턴/wonjun_/views/layout.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
{{title}}
5 |
6 |
7 |
8 | {{{body}}}
9 |
10 |
11 |
--------------------------------------------------------------------------------
/07_모델_뷰_패턴/wonjun_/views/mvc.hbs:
--------------------------------------------------------------------------------
1 |
{{title}}
2 |
7 |
8 | {{#each users}}
9 | {{this.lastName}} {{this.firstName}}
10 | {{/each}}
11 |
--------------------------------------------------------------------------------
/08_웹_패턴/READMD.md:
--------------------------------------------------------------------------------
1 | # 8. 웹 패턴
2 |
--------------------------------------------------------------------------------
/09_메시징_패턴/BHKIM.md:
--------------------------------------------------------------------------------
1 | # 9. 메시징 패턴
2 |
3 | 1. 메시지란 무엇인가
4 | 1-1. 명령
5 | 1-2. 이벤트
6 | 2. 요청-응답 (Request-reply)
7 | 3. 발행-구독 (Publish-subscribe)
8 | 3-1. 팬아웃과 팬인 (Fan out and fan in)
9 | 4. 데드-레터 큐 (Dead-letter queues)
10 | 5. 메시지 재생
11 | 6. 파이프와 필터
12 |
13 | ### 요청-응답 (Request-reply)
14 | ```js
15 | class CrowMailRequestor {
16 | Request() {
17 | var message = { __messageDate: new Date(),
18 | __from: "requestor",
19 | __corrolationId: Math.random(),
20 | body: "Invade Most Cailin" };
21 | var bus = new CrowMailBus(this);
22 | bus.Send(message);
23 | console.log("Message sent!");
24 | }
25 | processMessage(message) {
26 | console.dir(message);
27 | }
28 | }
29 | class CrowMailResponder {
30 | constructor(bus) {
31 | this.bus = bus;
32 | }
33 | processMessage(message) {
34 | var response = { __messageDate: new Date(),
35 | __from: "responder",
36 | __corrolationId: message.__corrolationId,
37 | body: "Okay invaded." };
38 | this.bus.Send(response);
39 | console.log("Reply sent");
40 | }
41 | }
42 | class CrowMailBus {
43 | constructor(requestor) {
44 | this.requestor = requestor;
45 | this.responder = new CrowMailResponder(this);
46 | }
47 | Send(message) {
48 | if (message.__from == "requestor") {
49 | this.responder.processMessage(message);
50 | }
51 | else {
52 | this.requestor.processMessage(message);
53 | }
54 | }
55 | }
56 | var requestor = new CrowMailRequestor();
57 | requestor.Request();
58 | ```
59 |
60 | ### 발행-구독 (Publish-subscribe)
61 | ```js
62 | class CrowMailRequestor {
63 | constructor(bus) {
64 | this.bus = bus;
65 | }
66 | Request() {
67 | var message = { __messageDate: new Date(),
68 | __from: "requestor",
69 | __corrolationId: Math.random(),
70 | __messageName: "FindSquareRoot",
71 | body: "Hello there. What is the square root of 9?" };
72 | this.bus.Subscribe("SquareRootFound", this);
73 | this.bus.Send(message);
74 | console.log("message sent!");
75 | }
76 | processMessage(message) {
77 | console.log("I got");
78 | console.dir(message);
79 | }
80 | }
81 | class CrowMailResponder {
82 | constructor(bus) {
83 | this.bus = bus;
84 | }
85 | processMessage(message) {
86 | var response = { __messageDate: new Date(),
87 | __from: "responder",
88 | __corrolationId: message.__corrolationId,
89 | __messageName: "SquareRootFound",
90 | body: "Pretty sure it is 3." };
91 | this.bus.Publish(response);
92 | console.log("Reply published");
93 | }
94 | }
95 | class CrowMailBus {
96 | constructor() {
97 | this.responder = new CrowMailResponder(this);
98 | this.responders = [];
99 | }
100 | Send(message) {
101 | if (message.__from == "requestor") {
102 | this.responder.processMessage(message);
103 | }
104 | }
105 | Publish(message) {
106 | for (var i = 0; i < this.responders.length; i++) {
107 | if (this.responders[i].messageName == message.__messageName) {
108 | (function (b) {
109 | b.subscriber.processMessage(message);
110 | })(this.responders[i]);
111 | }
112 | }
113 | }
114 | Subscribe(messageName, subscriber) {
115 | this.responders.push({ messageName: messageName, subscriber: subscriber });
116 | }
117 | }
118 | class TestResponder1 {
119 | processMessage(message) {
120 | console.log("Test responder 1: got a message");
121 | }
122 | }
123 | class TestResponder2 {
124 | processMessage(message) {
125 | console.log("Test responder 2: got a message");
126 | }
127 | }
128 | var bus = new CrowMailBus();
129 | bus.Subscribe("SquareRootFound", new TestResponder1());
130 | bus.Subscribe("SquareRootFound", new TestResponder2());
131 | var requestor = new CrowMailRequestor(bus);
132 | requestor.Request();
133 | ```
134 |
135 | ### 팬아웃과 팬인 (Fan out and fan in)
136 | ```js
137 | class Combiner {
138 | constructor() {
139 | this.waitingForChunks = 0;
140 | }
141 | combine(ingredients) {
142 | console.log("Starting combination");
143 | if (ingredients.length > 10) {
144 | for (var i = 0; i < Math.ceil(ingredients.length / 2); i++) {
145 | this.waitingForChunks++;
146 | console.log("Dispatched chunks count at: " + this.waitingForChunks);
147 | var worker = new Worker("FanOutInWebWorker.js");
148 | worker.addEventListener('message', (message) => this.complete(message));
149 | worker.postMessage({ ingredients: ingredients.slice(i, i * 2) });
150 | }
151 | }
152 | }
153 | complete(message) {
154 | this.waitingForChunks--;
155 | console.log("Outstanding chunks count at: " + this.waitingForChunks);
156 | if (this.waitingForChunks == 0)
157 | console.log("All chunks received");
158 | }
159 | }
160 |
161 | // FanOutInWebWorker.js
162 | self.addEventListener('message', function (e) {
163 | var data = e.data;
164 | var ingredients = data.ingredients;
165 | combinedIngredient = new CombinedIngredient();
166 | for (var i = 0; i < ingredients.length; i++) {
167 | combinedIngredient.Add(ingredients[i]);
168 | }
169 | console.log("calculating combination");
170 | setTimeout(combinationComplete, 2000);
171 | }, false);
172 | function combinationComplete() {
173 | console.log("combination complete");
174 | self.postMessage({ event: 'combinationComplete', result: combinedIngredient });
175 | }
176 | ```
177 | eg. [fanoutin.html](./bhkim_/fanoutin.html)
178 |
179 | ### 메시지 버전 관리
180 | ```js
181 | module Login{
182 | export class CreateUserv1Message implements IMessage{
183 | __messageName: string
184 | UserName: string;
185 | FirstName: string;
186 | LastName: string;
187 | EMail: string;
188 | }
189 |
190 | export class CreateUserv2Message implements IMessage{
191 | __messageName: string;
192 | UserTitle: string;
193 | }
194 |
195 | export class CreateUserv1tov2Upgrader{
196 | constructor(public bus: IBus){}
197 | public processMessage(message: CreateUserv2Message){
198 | message.__messageName = "CreateUserv1Message";
199 | delete message.UserTitle;
200 | this.bus.publish(message);
201 | }
202 | }
203 |
204 | export interface IBus{
205 | publish(IMessage);
206 | }
207 | export interface IMessage{
208 | __messageName: string;
209 | }
210 | }
211 | ```
212 |
--------------------------------------------------------------------------------
/09_메시징_패턴/DOHYUNG.md:
--------------------------------------------------------------------------------
1 | # 8장, 9장, 11장 일부 정리
2 |
3 | ## 8장
4 |
5 | - 프로미스 패턴
6 | - 지연 평가를 기반에 둠
7 | - 자연스럽게 비동기에 많이 의존할 수 밖에 없는 자바스크립트 프로그램을 효율적으로 제어하는데 도움이 된다
8 |
9 | ## 9장 (Messaging Patterns)
10 |
11 | - 메세지의 전파를 통해 확장도 용이하고 단일 애플리케이션에서도 결합도를 낮추고 테스트를 용이하게 한다.
12 | - 메세지는 서로 연관된 의미를 가지고 있는 데이터의 모음이라고 할 수 있다.
13 | - 메세지는 애플리케이션이나 비지니스에서 행하는 액션과 관련이 있다. 메세지는 수신자가 액션을 실행하기 위해 필요한 모든 정보를 가지고 있다.
14 | - 대부분의 메세징 시스템에서는 봉투(envelope)를 정의하고 있다. 이 봉투는 메세지 적합성 인증, 라우팅, 보안 등에 활용되는 메타데이터이다.
15 | - 메세지는 봉인되어야 하고 한번 생성되면 변경사항이 있어선 안된다. 그래야 메세지를 추적하고 답신하기 쉬워진다.
16 | - 보통 단일 프로세스에서 동작하는 메세지는 동기적으로 다루어진다. 메인 프로세스가 효과적으로 메세지를 처리할 때 까지 기다리는 식이다. 하지만 자바스크립트에서는 이를 비동기로 처리하도록 장려한다.
17 | - 비동기 메세지를 처리할 때 순서가 보장되도록 잘 관리하는 등의 별도 처리가 필요하다.
18 | - 커맨드와 이벤트로 이루어져 있다. 커맨드는 ‘어떻게 해야하는지’ 전달한다고 하면 이벤트는 ‘무슨 일이 일어날 때’ 반응하도록 한다.
19 | - 커맨드
20 | - 단순한 데이터 전달 객체이다.
21 | - 명령형으로 보이도록 (동사)(대상) 의 형식으로 이루어져있다. 무난한 이름을 피하고 이 커맨드가 무슨 일을 일으키는지 명확하게 표현하는 것이 중요하다.
22 | - 메세지 자체의 유용성을 높이려면 해당 비지니스의 의미를 잘 내포한 구성 요소를 가지고 있어야 한다. 이 분야를 어떻게 효과적으로 표현하고자 하는지는 많은 연구가 진행되고 있는데, Domain-Driven-Design(DDD)가 대표적인 예이다.
23 | - 커맨드는 그 특정 구성 요소에게 어떤 일을 하라고 하는지 정확하게 알려주는 것이다.
24 | - 특정 커맨드를 받아들이기 위한 엔드포인트는 딱 하나 존재한다. 이렇게 만듦으로서 엔드포인트가 메세지의 유효성을 검사하고 취소하더라도 애플리케이션의 다른 부분에 영향을 주지 않는다.
25 | - 이벤트
26 | - 변경하거나 취소하는 것에 대한 정의가 없다. 이벤트는 단순히 무슨 일이 일어날때 알려주는 것이고, 과거를 바꿀 순 없기 때문이다.
27 | - 보통은 과거형으로 작성한다
28 | - 커맨드와는 달리 여러 컴포넌트로부터 수신 가능하다
29 | - 브라우저에서 일어나는 이벤트는 저자가 정의한 것과는 꽤 다른 모습을 보이고 있기는 하다. 이벤트 핸들러가 취소를 할수도 있고 다음 핸들러로 이벤트가 넘어가는걸 막을 수도 있기 때문이다.
30 | - 이벤트가 다형성을 띄는 경우도 있다.
31 | - 요청-응답 패턴
32 | - 대부분의 경우 커맨드를 전송하는 일은 비동기 작업이기 때문에 ID별로 레코드를 검색해보는 등의 행동을 하기 쉽지 않다. 대신에 레코드를 검색하고 나서 그에 맞는 이벤트가 리턴되기를 기다려야 한다.
33 | - 그닥 권장되는 패턴은 아니다.
34 | - RabbitMQ, ZeroMQ 같은 메세지 전달 시스템이 있으니 조사해보는것이 좋다.
35 | - 발행-구독 패턴
36 | - 코드 처리와 이벤트를 분리하는데 아주 유용한 방법
37 | - 메세지 발행자 측에서는 보내자마자 최대한 빨리 메세지 전달이 끝나도록 하는게 핵심이다. 적합한 형태를 갖춘 메세지를 만들고 그를 수신자에게 전달하기만 하면 된다.
38 | - 요청-응답 패턴에서 만들었던 버스를 조금 더 발전시켜 라우터 역할을 하게 만드는데, 메세징을 할 때 그 속성을 검사하고 그에 따라 다른 행동을 하게 하는 비지니스 로직이 들어가면 버스의 속도가 느려지고 디버깅하기 어려워진다.
39 | - 펼쳤다 접기
40 | - CPU가 발전하면서 멀티 스레드 처리 및 동시성을 처리할 때 곤란한 경우가 많이 생기게 되었다. 메세지는 입력과 출력 자체에만 집중하게 하여 문제를 해결하는 듯 보인다.
41 | - 검색의 경우 여러 입력을 하나의 메세지로 묶을 수 있고, 약 1만개의 문서가 있다고 할 때 검색 부위를 4~5개로 나누어서 다섯 개의 메세지를 각각의 부위별로 검색하게 하여 나중에 결과를 합칠 수도 있다.
42 | - 브라우저에서는 웹 워커를 사용하여 펼치기 전략을 취할 수 있다. (여러 개의 코어에 접근할 수 있다)
43 | - 각각의 노드가(혹은 워커가) 처리하는 문제가 동일한 문제일 필요는 없으나, 메세지를 전달하면서 생기는 오버헤드보다 이 노드를 생성해서 처리하는 비용이 더 적게 만들어야 할 것이다.
44 | - Dead-letter queues
45 | - 메세지 시나리오에서 실패하는 경우를 다루는건 쉽다. 실패를 다루는 전략의 핵심은 에러를 받아들이는 것이다.
46 | - 비동기 시스템에서는 에러가 일어나는대로 바로 처리될 필요가 없다. 대신 다른 곳에(에러 큐) 치워두고 실제 사용하는 사람이 나중에 확인하게 만들면 된다.
47 | - 에러가 일어날 때 어떤 속성에서 문제가 생겼는지 등을 기록하며 쌓아두면서 에러를 바로잡아 갈 수록 메세지 핸들러의 질은 높아진다. 적절하게 테스트가 완료될 때 까지 급하게 프로덕션 앱을 고치려고 할 필요도 없다. 더 정확한 시스템을 향한 과정은 계속되어야 하고 그 신뢰도를 유지해야 한다.
48 | - 전통적인 시스템이라면 웹 서비스에 문제가 생기는게 메세지 핸들러의 문제로 연결되지만, 메세지 기반의 시스템이라면 문제가 생겼던 명령은 한번 뒤로 돌아와서 재시도를 해보는 등의 변화를 가져갈 수 있다. 이런 방식의 접근은 작은 문제를 부드럽게 넘어가면서 큰 문제로 번지는 것을 막을 수 있다.
49 | - 메세지 반복
50 | - 에러가 일어나는 메세지를 다루는 것도 중요하지만 그 메세지를 재처리하는 일도 중요하다. 심지어 에러가 없다 하더라도 사용자의 일반적인 사용 패턴을 반영하기 때문에 메세지를 검사하는 큐에서 들어오는 데이터를 테스트에 활용할 수도 있다.
51 | - 어떤 데이터가 왜 바뀌었는지 파악하는 것이 중요하다. (메세지 변경의 히스토리를 남기는 것)
52 | - 메세지 반복을 내부에서 처리하게 될 때 서비스 전체가 문제가 생긴다면 그 메세지를 복기하는 것 조차 불가능하다. 현실적으로 메세지 반복을 도입해야한다고 생각한다면 외부 메세지 버스를 도입하는게 답이다.
53 | - 파이프와 필터
54 | - 메세지는 불변 객체로 다루어야 한다고 언급했지만, 메세지가 다시 발송되지 말아야 하거나 새로운 타입으로 내보내는 것을 하지 말라는 이야기는 아니다.
55 | - 각각의 잘게 쪼개진 연결체들(마이크로서비스)는 어떤 메세지를 받고 자신이 어떤 결과를 내놓아야하는지만 집중하고 있다. 자연스럽게 결합도는 떨어지고 새로운 기능을 추가하기 용이해진다.
56 | - 서비스는 각각의 메세지 소비자들이 파이프처럼 넘기듯이 메세지를 연결할 수 있고, 필터링 되는 것 처럼 메세지를 재생산할 수 있다.
57 | - 메세지 버저닝
58 | - 시스템이 발전하면서 메세지에 담기는 정보도 변경될 수 있다. 이에 따라 효과적으로 데이터를 다룰 때 메세지를 버저닝하는 것이 유용하다.
59 | - 보통은 기존 메세지의 구조를 이어받아 확장하는 식으로 버저닝을 하게 된다. (타입스크립트의 인터페이스 상속을 하면 굉장히 유용하다)
60 | - 모든 애플리케이션이 메세징을 도입한다고 잘 동작하는건 아니다. 상호 협력적이고 데이터 손실을 피하면서 기록을 열심히 남겨야 하는 환경이 적합한 후보가 된다. 대부분의 경우 일반적인 CRUD 구성으로도 충분하다.
61 |
62 | ## 11장 고급 패턴
63 |
64 | - 의존성 주입
65 | - 프로젝트 규모가 커질 수록 기능을 직접 주입하는 일은 점점 어려워진다. 우리가 객체 생성 자체를 서비스로 생각할 때 문제를 해결하기 쉬워진다. 객체 생성을 중앙에서 관리하면 그 부분에서만 변경 사항을 다루는 것이다. 새 구현은 어떻게든 해당 인터페이스를 만족하고 있으니 걱정할 필요가 없어진다.
66 | - 그러나 자바나 C#같은데서는 타입과 인터페이스로 의존성 주입을 명확하게 지정할 수 있는 반면 자바스크립트에서는 변수명 정도에 의존할 수 밖에 없다는 문제가 있다. 이는 보통 프로덕션에서 자바스크립트 파일을 축소할 때 문제를 야기할 수도 있다.
--------------------------------------------------------------------------------
/09_메시징_패턴/READMD.md:
--------------------------------------------------------------------------------
1 | # 9. 메시징 패턴
2 |
--------------------------------------------------------------------------------
/09_메시징_패턴/bhkim_/FanOutInWebWorker.js:
--------------------------------------------------------------------------------
1 | class Ingredient {}
2 | class CombinedIngredient {
3 | Add(ingredient) {
4 | }
5 | }
6 | var combinedIngredient;
7 | self.addEventListener('message', function (e) {
8 | var data = e.data;
9 | var ingredients = data.ingredients;
10 | combinedIngredient = new CombinedIngredient();
11 | for (var i = 0; i < ingredients.length; i++) {
12 | combinedIngredient.Add(ingredients[i]);
13 | }
14 | console.log("calculating combination");
15 | setTimeout(combinationComplete, 2000);
16 | }, false);
17 | function combinationComplete() {
18 | console.log("combination complete");
19 | self.postMessage({ event: 'combinationComplete', result: combinedIngredient });
20 | }
21 |
--------------------------------------------------------------------------------
/09_메시징_패턴/bhkim_/fanoutin.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Fan In Out
6 |
7 |
8 |
9 |
10 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/09_메시징_패턴/jaenam.md:
--------------------------------------------------------------------------------
1 | # CH 9. 메시징 패턴
2 |
3 | ## 3. 발행-구독 (Publish-subscribe)
4 |
5 | ```js
6 | class Publisher {
7 | constructor (name) {
8 | this.subscribers = new Set()
9 | this.name = name
10 | }
11 | subscribe (subscriber) {
12 | this.subscribers.add(subscriber);
13 | }
14 | unsubscribe (subscriber) {
15 | this.subscribers.delete(subscriber);
16 | }
17 | publish (item) {
18 | for(let sub of this.subscribers) {
19 | sub.receive(this.name, item);
20 | }
21 | }
22 | }
23 | class Newsletter extends Publisher {
24 | constructor () {
25 | super('뉴스레터')
26 | }
27 | }
28 | class Magazine extends Publisher {
29 | constructor () {
30 | super('잡지')
31 | }
32 | }
33 |
34 | class Subscriber {
35 | constructor (name) {
36 | this.name = name
37 | }
38 | subscribe (target) {
39 | target.subscribe(this)
40 | }
41 | unsubscribe (target) {
42 | target.unsubscribe(this)
43 | }
44 | receive (target, item) {
45 | if(this.constructor.name === 'Subscriber') {
46 | throw new Error('this method should be invoked by an instance of sub class');
47 | }
48 | }
49 | }
50 | class Student extends Subscriber {
51 | receive (target, item) {
52 | console.log(`${this.name}이(가) ${target}의 ${item}을(를) 스크랩했지만 읽을 생각은 없다.`)
53 | }
54 | }
55 | class Teacher extends Subscriber {
56 | receive (target, item) {
57 | console.log(`${this.name}이(가) ${target}의 ${item}을(를) 읽기는 했으나 이해하진 못했다.`);
58 | }
59 | }
60 | const sportsSeoul = new Newsletter()
61 | const bigissue = new Magazine()
62 | const jn = new Student('젠남')
63 | const wj = new Teacher('웡쥰')
64 | const dh = new Teacher('두형')
65 | jn.subscribe(sportsSeoul)
66 | jn.subscribe(bigissue)
67 | wj.subscribe(sportsSeoul)
68 | sportsSeoul.publish('4월 1일 조간신문')
69 | bigissue.publish('빅이슈 3월호')
70 |
71 | dh.subscribe(sportsSeoul)
72 | dh.subscribe(bigissue)
73 | sportsSeoul.publish('4월 1일 석간신문')
74 | bigissue.publish('빅이슈 3월호 2쇄')
75 |
76 | jn.unsubscribe(sportsSeoul)
77 | sportsSeoul.publish('4월 2일 조간신문')
78 | bigissue.publish('빅이슈 4월호')
79 | ```
80 |
--------------------------------------------------------------------------------
/10_테스트를_위한_패턴/READMD.md:
--------------------------------------------------------------------------------
1 | # 10. 테스트를 위한 패턴
2 |
--------------------------------------------------------------------------------
/11_고급_패턴/READMD.md:
--------------------------------------------------------------------------------
1 | # 11. 고급 패턴
2 |
--------------------------------------------------------------------------------
/11_고급_패턴/jaenam.md:
--------------------------------------------------------------------------------
1 | # CH 11. 고급 패턴
2 |
3 | ## 의존성 주입
4 |
5 | ```js
6 | const Util = function () {}
7 | const Ajax = function () {}
8 | const Event = function () {}
9 |
10 | const Foo = define([
11 | 'Util',
12 | 'Ajax',
13 | 'Event'
14 | ], function (util, ajax, event) {
15 | var i = 0;
16 | function increase() {
17 | i++;
18 | }
19 | function get() {
20 | return i;
21 | }
22 | return {
23 | increase: increase,
24 | get: get
25 | };
26 | });
27 |
28 | /* js/first.js */
29 | const First = define([
30 | 'Foo'
31 | ], function (foo) {
32 | foo.increase();
33 | return {
34 | getFooValue: function () {
35 | return foo.get();
36 | }
37 | };
38 | });
39 |
40 | const Second = define([
41 | 'Foo'
42 | ], function (foo) {
43 | return {
44 | getFooValue: function () {
45 | return foo.get();
46 | }
47 | };
48 | });
49 |
50 | require([
51 | 'First',
52 | 'Second'
53 | ], function (first, second) {
54 | console.log(first.getFooValue()); // 1
55 | console.log(second.getFooValue()); // 1
56 | });
57 | ```
58 |
--------------------------------------------------------------------------------
/12_오늘날의_ES6_솔루션/READMD.md:
--------------------------------------------------------------------------------
1 | # 12. 오늘날의 ES6 솔루션
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 자바스크립트 디자인 패턴 스터디
2 |
3 | 자바스크립트 디자인 패턴 - acorn 출판사 / 독서 및 토론 스터디
4 |
5 | @ 2018. 03.
6 |
7 |
8 | ## 진행방식
9 |
10 | 1. 진도별 담당자를 정해서 준비해오고
11 |
12 | 2. 스터디시간에 책을 읽으면서 중간중간 코드리뷰 / 토론 진행.
13 |
14 |
15 | ## 멤버
16 |
17 | 정재남 . 황원준 . 이지나 . 백성훈 . 현진호 .
18 | 김병화 . 박지은 . 안도형 . 이정현 . 이창규
19 |
20 |
21 | ## 계획 (가안)
22 |
23 | - 3/5
24 | - 1 재미와 이익을 주는 설계
25 | - 2 코드 구성 > 원준
26 | - 3 생성 패턴 > 재남
27 | - 3/12
28 | - 4 구조 패턴
29 | - 5 행동 패턴
30 | - 3/19
31 | - 6 함수형 프로그래밍
32 | - 7 모델 - 뷰 패턴
33 | - 3/26
34 | - 8 웹 패턴
35 | - 9 메시징 패턴
36 | - 4/2
37 | - 10 테스트를 위한 패턴
38 | - 11 고급 패턴
39 | - 12 오늘날의 ES6 솔루션
40 |
--------------------------------------------------------------------------------