Abstract Loop - Generator
const Operator = class {
static factory(v) {
if (v instanceof Object) {
// Object.values - hasOwnProperty 까지 다 처리되어있다.
if (!Array.isArray(v)) v = Object.values(v);
return new ArrayOp(v.map(v => Operator.factory(v)));
} else return new PrimaOp(v);
}
constructor(v) {this.v = v;}
*gene() {throw 'override';}
};
const PrimaOp = class extends Operator {
constructor(v) {super(v);}
*gene(){yield this.v;}
};
const ArrayOp = class extends Operator {
constructor(v) {super(v);}
*gene(){for (const v of this.v) yield * v.gene();}
};
for (const v of Operator.factory([1,2,3,{a:4, b:5}, 6, 7]).gene()) console.log(v)
// 1
// 2
// 3
// 4
// 5
// 6
// 7
이전에 배웠던 코드를 Generator로 바꿨다.
아래는 이전에 작성한 코드이다.
// 팩토리
const Operator = class {
static factory(v){
if (v instanceof Object) {
// Object.values - hasOwnProperty 까지 다 처리되어있다.
if (!Array.isArray(v)) v = Object.values(v);
return new ArrayOp(v.map(v => Operator.factory(v)));
}else return new PrimaOp(v);
}
constructor(v) {this.v = v;}
operation(f) {throw 'override';}
};
// 컴포지트
const PrimaOp = class extends Operator{
constructor(v) {super(v);}
operation(f){f(this.v);}
}
const ArrayOp = class extends Operator {
constructor(v) {super(v);}
operation(f){for (const v of this.v) v.operation(f);}
}
// operation 메서드를 통해 우리가 해야될 것들을 각 객체에 정의했다.
// 이렇게 하기 싫다면?
// operation 에 해당하는 애들을 generator에게 미루면 된다.
Operator.factory([1,2,3, {a:'abc', b:5}, 6, 7]).operation(console.log);
// 1
// 2
// 3
// a
// b
// c
// 5
// 6
// 7
const Operator = class {
static factory(v) {
if (v instanceof Object) {
// Object.values - hasOwnProperty 까지 다 처리되어있다.
if (!Array.isArray(v)) v = Object.values(v);
return new ArrayOp(v.map(v => Operator.factory(v)));
} else return new PrimaOp(v);
}
constructor(v) {this.v = v;}
*gene() {throw 'override';}
};
const PrimaOp = class extends Operator {
constructor(v) {super(v);}
// yield 값을 반환하고 중지
*gene(){yield this.v;}
};
const ArrayOp = class extends Operator {
constructor(v) {super(v);}
// yield * 다음 나오는 걸 전부 yield 처리한 후 그 다음 문으로 넘어간다.
// v.gene() 도 이터러블 객체
// 즉 아래 for 루프가 돌려면 각각 v마다 다 돌고 그 다음 v로 넘어간다.
*gene(){for (const v of this.v) yield * v.gene();}
};
// 그 덕분에 for 루프 안에다 제너레이터를 넣을 수 있게 되었다.
// 아까처럼 operation처럼 특수 구문을 만들어 실행시킨 것이 아닌 자바스크립트 표준 문법을 수용해서 작성하면 된다.
for (const v of Operator.factory([1,2,3,{a:4, b:5}, 6, 7]).gene()) console.log(v)
위 코드 해석
Generator의 yield는 iterable 객체를 반환한다.
- [1,2,3,{a:4, b:5}, 6, 7] 값이 Operator 객체의 static 메서드인
factory
의v
값으로 들어간다. - v instanceof Object에서 true를 반환한다. : [1,2,3,{a:4, b:5}, 6, 7] 는 객체이다.
- !Array.isArray(v)에서 false를 반환한다. : [1,2,3,{a:4, b:5}, 6, 7] 는 배열이다. (객체이면서 배열, 배열은 객체이다.)
new ArrayOp(v.map(v => Operator.factory(v))) 값을 반환한다.
- 즉, new ArrayOp([Operator.factory(1), Operator.factory(2), Operator.factory(3), Operator.factory({a:4, b:5}), Operator.factory(6), Operator.factory(7)]) 을 반환한다.
- 각각 값이 객체인지 아닌지, 객체라면 배열인지 아닌지에 따라 다음과 같이 반환한다.
new ArrayOp([PrimaOp(1), PrimaOp(2), PrimaOp(3), ArrayOp([Operator.factory(4), Operator.factory(5)]), PrimaOp(6), PrimaOp(7)]) new ArrayOp([PrimaOp(1), PrimaOp(2), PrimaOp(3), ArrayOp([PrimaOp(4), PrimaOp(5)]), PrimaOp(6), PrimaOp(7)])
- ArrayOp의 gene 메서드 : PrimaOp(1).gene(); -> yield 1
- ArrayOp의 gene 메서드 : PrimaOp(2).gene(); -> yield 2
ArrayOp의 gene 메서드 : PrimaOp(2).gene(); -> yield 3
- ArrayOp의 gene 메서드 : PrimaOp(4).gene(); -> yield 4
ArrayOp의 gene 메서드 : PrimaOp(5).gene(); -> yield 5
- ArrayOp의 gene 메서드 : PrimaOp(6).gene(); -> yield 6
ArrayOp의 gene 메서드 : PrimaOp(7).gene(); -> yield 7
- for (const v of [1,2,3,4,5,6,7]) console.log(v)
함수 객체를 받지 않고 제어문을 추상화하는데 성공했다.
그 덕분에 우리는 똑같은 제어구조 안에서 매번 문을 다시 만들어야 했는데(operation), 이제는 맨 뒤의 문만 바꿔주면 된다.
위 코드로 문법적인 혜택을 다 누릴 수 있다. (spread, 해체할당, for … of 등)