🌕🌑🌑
🔥 코드스피츠 수업을 수강하면서 복습한 내용을 정리했습니다. 아직 정리중..
1. Stage Stage 클래스는 뭘 필요로 할까?
판 마지막 판이 몇 판일까? = 몇 판까지 있을까?
속도 ** 판마다 속도가 증가한다.**
** 속도는 어떤 객체가 가져가야할까?** - Game보다는 Stage가 적합. - 캡슐화와 은닉화의 속성을 이용하여, 속도의 처리는 stage내에서만 처리하게 한 후, 외부에서는 최종 속도만 받을 수 있도록 한다. (getter) - 초기 속도
자기의 변화를 listener한테 통보하는 것으로 처리만 하고
listner의 형태를 직접 알 필요는 없게 한다.
1 2 const prop = (target, v ) => Object .assign (target, v);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 const Stage = class { constructor (last, min, max, listener ){ prop (this , {last, min, max, listener}); } clear ( ) { this .curr = 0 ; this .next (); } next ( ) { if (this .curr ++ < Stage .last ) { const rate = (this .curr - 1 ) / (this .last - 1 ); this .speed = this .min + (this .max - this .min ) * (1 - rate); this .listener (); } } }
2. Score 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const Score = class { constructor (listener ){ prop (this , {listener}); } clear ( ){ this .curr = 0 ; this .total = 0 ; } add (line, stage ){ const score = ???; this .curr += score; this .total += score; this .listener (); } }
cf__1 역할, 책임, 협력 프로그래밍의 실체는 수행해야하는 job이 누구의 역할과 책임으로 넘어가야하는지를 의사결정하는 행위.
🍡 객체지향 객체지향에서는 컨텍스트 라는 방법이 있다. 인스턴스별로 컨텍스트라는 유지한다. (컨텍스트: 인스턴스마다 고유하게 부여되어 있는 메모리).
**함수에서 값을 가져오는 방법 2가지. **
내가 인자로 값을 가져올지,
컨텍스트로 가져올지.
🍢 함수형 프로그래밍과 객체지향 프로그래밍의 차이점.
함수형 프로그래밍에서는 자유변수를 통해서 함수를 유지한다.
자유변수를 유지하기 위해서는 새로운 함수 생성이 필요하다. (클로저)
why? 함수가 태어날때 마다 자유변수로 인지하기 때문에.
🍭 객체지향을 통해서 클래스의 인스턴스를 만드는 행위를 함수형으로 바꾸면?
필요한 자유변수를 함수를 만들어서 리턴하는 행위와 같다.
그 함수가 컨텍스트 대신 자유변수로 해당 상태를 기억하고 있을테니까.
객체지향 에서 인스턴스의 수만큼 => 함수를 생성하는 걸로 함수지향 으로 바꿀 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 const Score = class { constructor (listener ){ prop (this , {listener}); } clear ( ){ this .curr = 0 ; this .total = 0 ; } add (line, stage ){ const score = stage.score (line); this .curr += score; this .total += score; this .listener (); } } const Stage = class { ... score (line ) { return parseInt ((this .curr * 5 ) * (2 ** line)) } }
스코어와 스테이지 간의 coupling 관계 현재는 약한 바인딩. add 함수 호출시에만 임시적으로 외부 인자로 들어오기때문에
But, 하나의 게임 안에서는 스테이지와 스코어를 동시에 소유하고 바뀌지 않는다.
게임에서의 스테이지 관리자와 스코어 관리자는 관계가 항구적. 즉, 게임이 진행되는 동안. 즉 매번 인자로 보내면 안된다.
맥락상 맞지 않다는 말. stage를 add함수의 인자로 보낸다는 것은 스코어를 더 할때마다 임시적으로 바인딩한다는 것인데 이는 위의 항구적인 관계와 맞지 않음. (코드의 의미가 맞지 않음.)
때문에 add의 인자가 아니라 컨텍스트 변수로 옮겨줘야한다.
도메인을 바라보고 어디 쪽의 역할이 맞는지 항상 의사결정을 해야한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const Score = class { constructor (👉stage, listener ){ prop (this , {stage, listener}); } clear ( ){ this .curr = 0 ; this .total = 0 ; } add (line ){ const score = 👉this .stage .score (line); this .curr += score; this .total += score; this .listener (); } } const Stage = class { ... score (line ) { return parseInt ((this .curr * 5 ) * (2 ** line)) } }
score와 stage간의 의존성이 생김
코드는 여러분들이 모국어로 쓰지 않기 때문에 동작만 하면 다 똑같은 코드로 보인다. 코드도 언어이기 때문에 한국어의 미묘한 늬앙스를 다양한 형사와 동사로 표현하는 것처럼,코드도 동작해도 표현방법에 따라서 늬앙스를 다 표현할 수 있다.
3. Block 클래스일까 인스턴스일까.
찍어낼 수 있어야 한다. => 클래스
부모클래스 >> 자식클래스
블럭 정의
테트리스 블럭은 회전을 할 수 있다.회전축, 회전점 을 정의하자.
세로와 가로의 모습을 보면 **2차원 배열**로 구현할 수 있다는 것이 보인다. (행과 열)
cf__2 연산은 데이터로 바꿀 수 있다.데이터 하나로 연산화 시킴
or 데이터 2개로 연산비용을 낮춤.
예전에는 머신이 낮고 메모리가 낮았기 때문에 연산을 중심으로 움직이고 메모리 비용을 낮추는 방향으로 갔음.
cpu 비용을 아끼고(연산비용을 줄이고) 메모리 비용을 사용하는 방향이 요즘 추세
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const Block = class { constructor (color ) { prop (this , {color, rotate :0 };) } left ( ) { if (--this .rotate < 0 ) this .rotate = 3 ; } right ( ) { if (++this .rotate > 3 ) this .rotate = 0 ; } getBlock ( ){throw 'override!' ;} } const blocks = [class extends Block , ...]
1 2 3 4 5 6 7 8 9 10 11 12 class extends Block { constructor ( ){ super ('#f8cbad' ); } getBlock ( ){ return this .rotate % 2 ? [[1 ], [1 ], [1 ], [1 ]] : [[1 ,1 ,1 ,1 ]] } }
1 2 3 4 5 6 7 8 9 10 11 12 13 class extends Block { constructor ( ){ super ('#f8cbad' ); } getBlock ( ){ switch (this .rotate ){ case 0 : return [[0 ,1 ,0 ], [1 ,1 ,1 ]] case 1 : return [[1 ,0 ], [1 ,1 ], [1 ,0 ]] case 2 : return [[1 ,1 ,1 ], [0 ,1 ,0 ]] case 3 : return [[0 ,1 ], [1 ,1 ], [0 ,1 ]] } } }
충분히 추상화가 되었을까? rotation은 부모클래스인 Block에서 관리 자식이 부모의 속성을 갖는 것은 은닉을 깨고 있는 것
부모 자식간에도 캡슐화와 은닉화가 성립해야한다.
this.rotate
로 접근하고 있다.
this.rotate % 2
부모의 rotate 정의에 자식이 맞추고 있다.
코드의 책임, 역할을 의인화 시켜서 생각하는 것이 좋다.
getBlcok()을 호출할 때마다 배열을 매번 생성하고 있다.
다시 개선
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 const Block = class { constructor (color, ✨✨...blocks ) { prop (this , { color, rotate :0 , ✨✨blocks, ✨✨count : blocks.length - 1 };) } left ( ) { if (--this .rotate < 0 ) this .rotate = 3 ; } right ( ) { if (++this .rotate > ✨✨count) this .rotate = 0 ; } getBlock ( ){ ✨✨ return this .blocks [this .rotate ]; } } const blocks = [ class extends Block { constructor ( ){ super ('#f8cbad' , [[1 ], [1 ], [1 ], [1 ]], [[1 ,1 ,1 ,1 ]] ); } } ]
4. Renderer 렌더러는 stage, score, block을 몰라도, data만 알아도 되는 구조
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const Renderer = class { constructor (col, row ){ prop (this , {col, row, blocks :[]}); while (row--) this .blocks .push ([]); } clear ( ){throw 'override' ;} render (data ){ if (!(data instanceof Data )) throw 'invalid data' ; this ._render (data); } _render (data ){throw 'override!' ;} }
Template Method Pattern 어떤 작업 알고리즘의 골격을 정의한다. 일부 단계는 서브 클래스에서 구현하도록 할 수 있다. 템플릿 메서드를 이용하면 알고리즘의 구조는 그대로 유지하면서 특정 단계만 서브 클래스에서 새로 정의하도록 할 수 있다.
4-0. Data(protocol) 1 2 3 4 5 6 const Data = class extends Array { constructor (row, col ){prop (this , {row, col});} }
es6는 클래스 내부에서 this를 바꿔 줄 수 있다.
4-1. Table Renderer 1 2 3 4 const el = el => document .createElement (el);const back = (s: pixel, v: color ) => s.backgroundColor = v;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 const TableRenderer = class extends Renderer { constructor (base, back, col, row ){ super (col, row); this .back = back; white (row-- ){ const tr = base.appendChild (el ('tr' )), curr = []; this .blocks .push (curr); let i = col; while (i--) curr.push (tr.appendChild ('td' ).style ); } this .clear (); } clear ( ){ this .blocks .forEach ( curr => curr.forEach (s => back (s, this .back )) ) } _render (v: Data ){ this .blocks .forEach ( (curr, row ) => curr.forEach ((s, col ) => back (s, v[row][col])) ) } }
변수 사용시 한 번 밖에 사용되지 않는데 변수로 잡는 것은 사실은 중복
for와 forEach 중 어떤걸 사용할까? 언어스팩에서 정의되어있는 메서드를 사용하자. forEach 성능문제는 우선 고려하지 말자.
4-2. Canvas Renderer 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const CanvasRenderer = class extends Renderer { constructor (base, back, col, row ){ suepr (col, row); prop (this , { width : base.width = parseInt (base.style .width ), height : base.height = parseInt (base.style .height ), cellSize : [base.width /col, base.height /row], ctx : base.getContext ('2d' ) }); } _render (v ){ const {ctx, cellSize :[w, h]} = this ; ctx.clearRect (0 , 0 , this .width , this .height ); let i = this .row ; while (i--){ let j = this .col ; while (j--){ ctx.fillStyle = v[i][j]; ctx.fillRect (j *w, j*h, w, h); } } } }
참고자료 https://www.bsidesoft.com/?p=2827 https://github.com/abhbtbb/tetris1