5장 유사한 코드 융합하기
의문점
- 성능 최적화는 프로파일링 도구 사용해서 가이드 해야함 (p.112)
- 인터페이스는 사용구의 일반적인 원입니다.
학습목표
- 유사 클래스 통합하기란?
- 조건부 산술로 구조 노출하기란?
- 간단한 UML 클래스 다이어그램 이해하기란?
- ‘전략 패턴 도입’으로 유사코드 도입하기란?
- ‘구현체가 하나뿐인 인터페이스 만들지 말것’으로 어지러움 정리하기
키워드
1. 유사한 클래스 통합하기
5.1 개요
updateTile() 은 우선 else 사용금지 규칙 위반함 → 첫번째 단계로 || 에 대해서 하나의 함수로 도입해야함
// Stone FallingStone의 유일한 차이점 2개 class Stone implements Tile { isAir() { return false; } isStone() { return true; } isFallingStone() { return false; } //차이 isBox() { return false; } isFallingBox() { return false; } isLock1() { return false; } isLock2() { return false; } draw(g: CanvasRenderingContext2D, x: number, y: number) { g.fillStyle = "#0000cc"; g.fillRect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE); } moveHorizontal(dx: number) { //차이 if (map[playery][playerx + dx + dx].isAir() && !map[playery + 1][playerx + dx].isAir()) { map[playery][playerx + dx + dx] = this; moveToTile(playerx + dx, playery); } } moveVertical(dy: number) { } } class FallingStone implements Tile { isAir() { return false; } isStone() { return false; } isFallingStone() { return true; } //차이 isBox() { return false; } isFallingBox() { return false; } isLock1() { return false; } isLock2() { return false; } draw(g: CanvasRenderingContext2D, x: number, y: number) { g.fillStyle = "#0000cc"; g.fillRect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE); } moveHorizontal(dx: number) { } //차이 moveVertical(dy: number) { } }
상수메서드(constant method)란 메서드가 상수를 반환할 때를 의미한다.
- 이것은 다른값을 반환하는 상수메서드 공유하기 때문에 두개 클래스 합칠 수있음
- 절차는 분수 더하는 알고리즘과 유사함
- 분수 더하는 알고리즘 : 분모 동일화
클래스 결합단계 : 상수메서드 제외 클래스 모든것 동일시 - 분수 더하는 알고리즘 : 실제 더하기
클래스 결합단계 : 클래스를 실제 합치기
- 분수 더하는 알고리즘 : 분모 동일화
절차
두개 moveHorizontal을 동일하게 만듬
- 본 메소드 주위에 if(true)추가
- isFallingStone() === false 로 바꾸기
- else 로 FallingStone 과 서로 합치기
상수 메서드 isFallingStone 만 다름 → falling 필드 도입 및 할당
bool대신(상수) this.falling 필드반환
문제확인 위해 컴파일
각 클래스
- falling 매개변수로 받기
- new Stone(false) 등에 인자전달
통합중인 클래스(Stone, FallingStone) 중 하나만 살리고 나머지 제거
class Stone implements Tile { //2단계 private falling : boolean; constructor(falling: boolean){ //5단계 falling을 매개변수로 받게하기 this.falling = falling; //false } isAir() { return false; } isStone() { return true; } //3단계 bool대신(상수) this.falling 필드반환 isFallingStone() { return this.falling; } //상수 대신 필드반환 isBox() { return false; } isFallingBox() { return false; } isLock1() { return false; } isLock2() { return false; } draw(g: CanvasRenderingContext2D, x: number, y: number) { g.fillStyle = "#0000cc"; g.fillRect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE); } moveHorizontal(dx: number) { //1단계 if(this.isFallingStone() === false){ if (map[playery][playerx + dx + dx].isAir() && !map[playery + 1][playerx + dx].isAir()) { map[playery][playerx + dx + dx] = this; moveToTile(playerx + dx, playery); } } else if(this.isFallingStone() === true){} } moveVertical(dy: number) { } } //마지막에 삭제 // class FallingStone implements Tile {
빈 if문, else 구문 포함되있는 규칙위반 문제
//추가 enum FallingState { FALLING, RESTING } //new Stone 에 추가 function transformTile(tile: RawTile) { switch (tile) { case RawTile.AIR: return new Air(); case RawTile.PLAYER: return new Player(); case RawTile.UNBREAKABLE: return new Unbreakable(); case RawTile.STONE: return new Stone(FallingState.RESTING); case RawTile.FALLING_STONE: return new Stone(FallingState.FALLING); case RawTile.BOX: return new Box(); case RawTile.FALLING_BOX: return new FallingBox(); case RawTile.FLUX: return new Flux(); case RawTile.KEY1: return new Key1(); case RawTile.LOCK1: return new Lock1(); case RawTile.KEY2: return new Key2(); case RawTile.LOCK2: return new Lock2(); default: assertExhausted(tile); } } //추가 function updateTile(x: number, y: number) { if ((map[y][x].isStone() || map[y][x].isFallingStone()) && map[y + 1][x].isAir()) { map[y + 1][x] = new Stone(FallingState.FALLING); map[y][x] = new Air(); } else if ((map[y][x].isBox() || map[y][x].isFallingBox()) && map[y + 1][x].isAir()) { map[y + 1][x] = new FallingBox(); map[y][x] = new Air(); } else if (map[y][x].isFallingStone()) { map[y][x] = new Stone(FallingState.RESTING); } else if (map[y][x].isFallingBox()) { map[y][x] = new Box(); } }
이렇게 하면 부울 인자를 제거해서 가독성 높아짐(mt : true, false 의미 생각X)
그러나 더 좋은 열거형 다루는 방법인 ‘클래스로 타입 코드 대체’ 리팩터링 패턴이 있음
interface FallingState{ isFalling() : boolean; isResting() : boolean; } class Falling implements FallingState{ isFalling(): boolean { return true; } isResting(): boolean { return false; } } class Resting implements FallingState{ isFalling(): boolean { return false; } isResting(): boolean { return true; } } class Stone implements Tile { // private falling : boolean; constructor(private falling: FallingState){ this.falling = falling; } isAir() { return false; } isStone() { return true; } // isFallingStone() { return this.falling } //상수대신 필드반환 // isFallingStone() { return this.falling === FallingState.FALLING; } // enum 방식 isFallingStone() { return this.falling.isFalling(); } // class 대체 방식 isBox() { return false; } isFallingBox() { return false; } isLock1() { return false; } isLock2() { return false; } draw(g: CanvasRenderingContext2D, x: number, y: number) { g.fillStyle = "#0000cc"; g.fillRect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE); } moveHorizontal(dx: number) { if(this.isFallingStone() === false){ if (map[playery][playerx + dx + dx].isAir() && !map[playery + 1][playerx + dx].isAir()) { map[playery][playerx + dx + dx] = this; moveToTile(playerx + dx, playery); } } else if(this.isFallingStone() === true){} } moveVertical(dy: number) { } }
isFallingStone 을 moveHorizontal 메서드에 인라인화 하려면 ‘클래스로 코드이관’ 사용
interface FallingState{ isFalling() : boolean; isResting() : boolean; moveHorizontal(tile : Tile, dx : number) : void; } class Falling implements FallingState{ isFalling(): boolean { return true; } isResting(): boolean { return false; } moveHorizontal(tile: Tile, dx: number): void { } } class Resting implements FallingState{ isFalling(): boolean { return false; } isResting(): boolean { return true; } moveHorizontal(tile: Tile, dx: number): void { if (map[playery][playerx + dx + dx].isAir() && !map[playery + 1][playerx + dx].isAir()) { map[playery][playerx + dx + dx] = tile; moveToTile(playerx + dx, playery); } } } class Stone implements Tile { // private falling : boolean; constructor(private falling: FallingState){ this.falling = falling; } isAir() { return false; } isStone() { return true; } // isFallingStone() { return this.falling } //상수대신 필드반환 // isFallingStone() { return this.falling === FallingState.FALLING; } // enum 방식 isFallingStone() { return this.falling.isFalling(); } // class 대체 방식 isBox() { return false; } isFallingBox() { return false; } isLock1() { return false; } isLock2() { return false; } draw(g: CanvasRenderingContext2D, x: number, y: number) { g.fillStyle = "#0000cc"; g.fillRect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE); } moveHorizontal(dx: number) { // 클래스로 코드 이관 -> 삭제 // if(this.isFallingStone() === false){ // if (map[playery][playerx + dx + dx].isAir() // && !map[playery + 1][playerx + dx].isAir()) { // map[playery][playerx + dx + dx] = this; // moveToTile(playerx + dx, playery); // } // } // else if(this.isFallingStone() === true){} //이관 this.falling.moveHorizontal(this, dx); } moveVertical(dy: number) { } }
삭제후 컴파일 하기 패턴
interface FallingState { isFalling() : boolean; // isResting() : boolean; moveHorizontal(tile : Tile, dx : number) : void; } class Falling implements FallingState { isFalling(): boolean { return true; } // isResting(): boolean { // return false; // } moveHorizontal(tile: Tile, dx: number): void { } } class Resting implements FallingState { isFalling(): boolean { return false; } // isResting(): boolean { // return true; // } moveHorizontal(tile: Tile, dx: number): void { if (map[playery][playerx + dx + dx].isAir() && !map[playery + 1][playerx + dx].isAir()) { map[playery][playerx + dx + dx] = tile; moveToTile(playerx + dx, playery); } } } class Stone implements Tile { // private falling : boolean; constructor(private falling: FallingState){ this.falling = falling; } isAir() { return false; } isStone() { return true; } isFallingStone() { return this.falling.isFalling(); } // class 대체 방식 isBox() { return false; } isFallingBox() { return false; } isLock1() { return false; } isLock2() { return false; } draw(g: CanvasRenderingContext2D, x: number, y: number) { g.fillStyle = "#0000cc"; g.fillRect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE); } moveHorizontal(dx: number) { this.falling.moveHorizontal(this, dx); } moveVertical(dy: number) { } isStony() { return true; } isBoxy() { return false; } }
- 5.1 리팩터링 패턴 : 유사 클래스 통합
- 설명
- 두 개 이상의 클래스에서 상수 메서드가 클래스에 따라 다른 값 반환할 때 마다, 이 패턴으로 클래 스통합가능
- 일련의 상수 메서드 집합을 기준(basis)라고 함
- 상수 메서드가 두개 일때 두개의 접점을 가진 기준임
- X개의 클래스를 통합하려면 최대 (X-1)개 접점을 가질 기준이 필요함
- 설명
2. 단순한 조건 통합하기
5.1 개요
interface Tile { isAir(): boolean; // isStone(): boolean; isFallingStone(): boolean; // isBox(): boolean; isFallingBox(): boolean; isLock1(): boolean; isLock2(): boolean; draw(g: CanvasRenderingContext2D, x: number, y: number): void; moveHorizontal(dx: number): void; moveVertical(dy: number): void; isStony(): boolean; isBoxy(): boolean; drop() : void; rest() : void; } class Stone implements Tile { // private falling : boolean; constructor(private falling: FallingState){ this.falling = falling; } isAir() { return false; } isStone() { return true; } // isFallingStone() { return this.falling } //상수대신 필드반환 // isFallingStone() { return this.falling === FallingState.FALLING; } // enum 방식 isFallingStone() { return this.falling.isFalling(); } // class 대체 방식 isBox() { return false; } isFallingBox() { return false; } isLock1() { return false; } isLock2() { return false; } draw(g: CanvasRenderingContext2D, x: number, y: number) { g.fillStyle = "#0000cc"; g.fillRect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE); } moveHorizontal(dx: number) { // 클래스로 코드 이관 // if(this.isFallingStone() === false){ // if (map[playery][playerx + dx + dx].isAir() // && !map[playery + 1][playerx + dx].isAir()) { // map[playery][playerx + dx + dx] = this; // moveToTile(playerx + dx, playery); // } // } // else if(this.isFallingStone() === true){} //이관 this.falling.moveHorizontal(this, dx); } moveVertical(dy: number) { } isStony() { return true; } isBoxy() { return false; } drop(): void { this.falling = new Falling(); } rest(): void { this.falling = new Resting(); } } //updateTile에서 바로 rest 메서드 사용할수있음 function updateTile(x: number, y: number) { if ((map[y][x].isStony()) && map[y + 1][x].isAir()) { map[y + 1][x] = new Stone(new Falling()); map[y][x] = new Air(); } else if ((map[y][x].isBoxy()) && map[y + 1][x].isAir()) { map[y + 1][x] = new Box(new Falling()); map[y][x] = new Air(); } //두개의 if문 내용이 동일하므로 ||로 합칠수있다. //하지만 이것을 if문 결합해서도 가능하다. // else if (map[y][x].isFallingStone() // || map[y][x].isFallingBox()) { // // map[y][x] = new Stone(new Resting()); // map[y][x].rest(); // } else if (map[y][x].isFalling()) { // map[y][x] = new Stone(new Resting()); map[y][x].rest(); } }
5.2 리팩터링 패턴 : if문 결합
설명
- 연속적으로 동일한 내용의 if문을 결합해서 중복 제거함
- || 을 추가해서 두식의 관계를 드러내기 때문에 유용함
절차
본문이 동일한지 확인함
첫번째 if문의 닫는괄호와 else if 문의 여는 괄호 사이의 코드를 선택하고 ‘삭제’한후 ‘||’ 추가
if(a){ //본문} else if (b){ //본문} // 변경 후 if (a||b) {//본문}
예제 : 송장(invoice) 으로 무엇을 할지 결정하는 로직
참고 : 업계에서 이를 상식이라 생각함
5.3 복잡한 조건 통합하기
updateTile 돌 → 공기, 공기 → 돌로 대체함
즉 돌 타일을 이동시키고 떨어지는 상태(falling) 로 결정하는것과 같음
function updateTile(x: number, y: number) { if ((map[y][x].isStony()) && map[y + 1][x].isAir()) { // map[y + 1][x] = new Stone(new Falling()); //아래 Else if 와 본문이 같아짐 map[y][x].drop(); map[y + 1][x] = map[y][x]; map[y][x] = new Air(); } else if ((map[y][x].isBoxy()) && map[y + 1][x].isAir()) { // map[y + 1][x] = new Box(new Falling()); map[y][x].drop(); map[y + 1][x] = map[y][x]; map[y][x] = new Air(); } else if (map[y][x].isFalling()) { map[y][x].rest(); } } //동일한 본문 조건 결합 function updateTile(x: number, y: number) { if (((map[y][x].isStony()) && map[y + 1][x].isAir()) ||((map[y][x].isBoxy()) && map[y + 1][x].isAir()) ) { //아래 Else if 와 본문이 같아짐 map[y][x].drop(); map[y + 1][x] = map[y][x]; map[y][x] = new Air(); } else if (map[y][x].isFalling()) { map[y][x].rest(); } }
조건을 위한 산술규칙 사용
- || 은 + 처럼 동작하고, && 는 * 처럼 동작함 → 정규산술법칙이 적용됨
- 부수적인 동작이 없다면 ’순수조건 사용(USE PURE CONDITIONS)’으로 부수적인 동작을 하지 않아야한다
규칙 : 순수조건 사용
정의
조건은 항상 순수조건이어야한다
순수란 조건에 부수적인 동작이 없을것
부수적 동작이란
- 조건이 변수에 값을 할당허거나
- 예외를 발생시키거나
- 출력, 파일 쓰기 등과 같이 I/O 와 상호작용 하는것
순수한 조건을 갖는것이 중요한 이유
- 부수적인 동작이 존재하는 조건 때문에 앞서 언급 규칙 이용X
- 부수적 동작은 조건문에서 흔히 사용안됨 추적해야하는데 더 많은 시간과 노력 투하
부수적인 동작은 책임을 분리시킬 것
캐시?
스멜
- 명령에서 질의분리 라는 일반적인 스멜에서 비롯됨
- 스멜은 부족용이 있는 모든것을 의미
- 질의는 순수한것을 의미
- 준수하는 방법은 void메소드 에서만 부수적 동작 허용하기
- 즉 ‘부수적인 동작’을 하거나 ‘무언가를 반환하는 것’중 하나 해야함
- 일반적인 스멜과 순수조건 사용의 차이점?
- 정의 보다는 호출에 초점을 맞춤
- 조건에 초점 맞추기 위해 이 스멜을 순화함
- 조건 이외에서 질의와 명령 혼합하는것은 리팩터링 결과에 영향안줌
- ‘메서드는 한가지 작업을 해야한다’에 근거함
의도
- 데이터를 가져오는 것과 변경하는 것을 분리하기
- 부수적인 동작은 위험한 전역상태의 일종
조건 산술적용 (저자 : 이 기법으로 버그 발견하고, 결과 예측가능해짐)
수학 방적식으로 변환
익숙한 산술법칙을 사용해서 단순화
다시 코드로 변환
//변환 전 function updateTile(x: number, y: number) { if (((map[y][x].isStony()) && map[y + 1][x].isAir()) ||((map[y][x].isBoxy()) && map[y + 1][x].isAir()) ) { //아래 Else if 와 본문이 같아짐 map[y][x].drop(); map[y + 1][x] = map[y][x]; map[y][x] = new Air(); } else if (map[y][x].isFalling()) { map[y][x].rest(); } } //변환 후 function updateTile(x: number, y: number) { //연산법칙으로 변환함 if ((map[y][x].isStony()) ||(map[y][x].isBoxy()) && map[y + 1][x].isAir() ) { //아래 Else if 와 본문이 같아짐 map[y][x].drop(); map[y + 1][x] = map[y][x]; map[y][x] = new Air(); } else if (map[y][x].isFalling()) { map[y][x].rest(); } } //클래스로 코드 이관 function updateTile(x: number, y: number) { // 클래스로 코드 이관 (도우미 메서드 이용) if ((map[y][x].canFall()) && map[y + 1][x].isAir() ) { //아래 Else if 와 본문이 같아짐 map[y][x].drop(); map[y + 1][x] = map[y][x]; map[y][x] = new Air(); } else if (map[y][x].isFalling()) { map[y][x].rest(); } }
클래스 간의 코드 통합
- 상당한 코드를 클래스로 이관함 → 인터페이스에 많은 메서드가 도입 됨 → ‘삭제후 컴파일’하기 이용 중간정리 적절함
- 돌, 상자 떨어지는 동작 통합위해 진행한 리팩터링 패턴을 ‘전략패턴 도입’이라고 함
클래스 관계묘사하는 UML 클래스 다이어그램 소개
- 클래스 다이어그램(이하 CD)은 인터페이스와 클래스의 구조가 서로 어떤관계인지 보여 줌
- CD에서 중요한것은 클래스와 인터페이스 간의 관계임
- ‘X가Y를 사용한다’, ‘X는 Y다’, ‘X가 하나 또는 여러 개의 Y를 가진다’ 세 범주로 나누어짐
- 이 중 ‘인터페이스 에서만 상속받을것’ 때문에 ‘구현 관계’을 사용하지 ‘상속 관계’ 사용불가
- 컴포지션과 집합의 차이는 어떻게 표현하는가 문제임
- 결국 컴포지션과 구현 두가지 관계 유형을 사용함
5.4 리팩터링 패턴 : 전략 패턴의 도입
설명
- 다른 클래스를 인스턴스화 해서 변형(variance)을 도입하는 개념
- 전략이 필드를 가지고있는 경우를 상태패턴이라고 함
- 상태패턴과 동일 즉 클래스를 추가해서 변경이 가능하게 하기
- 코드를 자체클래스로 옮기는것을 의미 → 새로운 변형추가 하지 않더라도 확장성 가질 수있음
상황
- 전략패턴의 변형은 늦은 바인딩의 궁긍적인 형태
- 첫번째, 코드에 변형 도입하고 싶어서 리팩터링 수행하는 경우 → 인터페이스가 필요
- 두번째, 떨어지는 성질을 코드화 했던 상황에서 → 바로 변형의 추가가 필요하다고 예상하지 않았을 때
- 즉 단지 클래스 간의 동작을 통합하려는 경우임
코드
// // 초기 코드 // // 최솟값 찾는 일괄처리 프로세서 // class ArrayMinium{ // constructor(private accumulator : number){ // } // process(arr : number[]){ // for(let i = 0 ; i < arr.length ; i++) // if(this.accumulator > arr[i]) // this.accumulator = arr[i]; // return this.accumulator; // } // } // class ArraySum{ // constructor(private accumulator : number){ // } // process(arr : number[]){ // for(let i = 0 ; i < arr.length ; i++) // this.accumulator += arr[i]; // return this.accumulator; // } // } // 1 메서드 추출 // class ArrayMinium{ // constructor(private accumulator : number){ // } // process(arr : number[]){ // for(let i = 0 ; i < arr.length ; i++) // this.processElement(arr[i]); // return this.accumulator; // } // processElement(e : number){ // if(this.accumulator > e) // this.accumulator = e; // } // } // class ArraySum{ // constructor(private accumulator : number){ // } // process(arr : number[]){ // for(let i = 0 ; i < arr.length ; i++) // this.accumulator += arr[i]; // return this.accumulator; // } // processElement(e : number){ // this.accumulator += e; // } // } // 2. 새로운 클래스 만들기 // class MiniumProcessor{ // } // class SumProcesssor{ // } // // 3. 생성자에 새로운 클래스를 초기화 함 // class ArrayMinium{ // private processor: MiniumProcessor; // constructor(private accumulator : number){ // this.processor = new MiniumProcessor(); // } // process(arr : number[]){ // for(let i = 0 ; i < arr.length ; i++) // this.processElement(arr[i]); // return this.accumulator; // } // processElement(e : number){ // if(this.accumulator > e) // this.accumulator = e; // } // } // class ArraySum{ // private processor: SumProcesssor; // constructor(private accumulator : number){ // this.processor = new SumProcesssor(); // } // process(arr : number[]){ // for(let i = 0 ; i < arr.length ; i++) // this.accumulator += arr[i]; // return this.accumulator; // } // processElement(e : number){ // this.accumulator += e; // } // } // // 4. 메서드를 각각 MiniumProcessor, SumProcesssor로 옮김 // class MiniumProcessor{ // processElement(e : number){ // if(this.accumulator > e) // this.accumulator = e; // } // } // class SumProcesssor{ // processElement(e : number){ // this.accumulator += e; // } // } // class ArrayMinium{ // private processor: MiniumProcessor; // constructor(private accumulator : number){ // this.processor = new MiniumProcessor(); // } // process(arr : number[]){ // for(let i = 0 ; i < arr.length ; i++) // this.processElement(arr[i]); // return this.accumulator; // } // processElement(e : number){ // this.processor.processElement(e); // } // } // class ArraySum{ // private processor: SumProcesssor; // constructor(private accumulator : number){ // this.processor = new SumProcesssor(); // } // process(arr : number[]){ // for(let i = 0 ; i < arr.length ; i++) // this.accumulator += arr[i]; // return this.accumulator; // } // processElement(e : number){ // this.processor.processElement(e); // } // } // 5. 두 경우 모두 accumulator 필드에 의존하므로 다음단계 수행함 // a. accumulator 필드를 MiniumProcessor, SumProcesssor 클래스로 옮기고 접근자를 만듬 // b. 새로운 접근자를 사용해서 원래 클래스에 발생하는 오류를 바로잡음 // class MiniumProcessor{ // constructor(private accumulator : number){ // } // processElement(e : number){ // if(this.accumulator > e) // this.accumulator = e; // } // getAaccumulator(){ // return this.accumulator; // } // } // class SumProcesssor{ // constructor(private accumulator : number){ // } // processElement(e : number){ // this.accumulator += e; // } // getAaccumulator(){ // return this.accumulator; // } // } // class ArrayMinium{ // private processor: MiniumProcessor; // constructor(accumulator : number){ // this.processor = new MiniumProcessor(accumulator); // } // process(arr : number[]){ // for(let i = 0 ; i < arr.length ; i++) // this.processElement(arr[i]); // return this.processor.getAaccumulator(); //접근자 사용해서 필드 획득 // } // processElement(e : number){ // this.processor.processElement(e); // } // } // class ArraySum{ // private processor: SumProcesssor; // constructor(accumulator : number){ // this.processor = new SumProcesssor(accumulator); // } // process(arr : number[]){ // for(let i = 0 ; i < arr.length ; i++) // this.processor.processElement(arr[i]); // return this.processor.getAaccumulator(); //접근자 사용해서 필드 획득 // } // processElement(e : number){ // this.processor.processElement(e); // } // } // 7 메서드 인라인화를 사용해서 1단계의 추출을 반대로 함 class MiniumProcessor{ constructor(private accumulator : number){ } processElement(e : number){ if(this.accumulator > e) this.accumulator = e; } getAaccumulator(){ return this.accumulator; } } class SumProcesssor{ constructor(private accumulator : number){ } processElement(e : number){ this.accumulator += e; } getAaccumulator(){ return this.accumulator; } } class ArrayMinium{ private processor: MiniumProcessor; constructor(accumulator : number){ this.processor = new MiniumProcessor(accumulator); } process(arr : number[]){ for(let i = 0 ; i < arr.length ; i++) this.processor.processElement(arr[i]); return this.processor.getAaccumulator(); //접근자 사용해서 필드 획득 } // processElement(e : number){ // this.processor.processElement(e); // } } class ArraySum{ private processor: SumProcesssor; constructor(accumulator : number){ this.processor = new SumProcesssor(accumulator); } process(arr : number[]){ for(let i = 0 ; i < arr.length ; i++) this.processor.processElement(arr[i]); return this.processor.getAaccumulator(); //접근자 사용해서 필드 획득 } // processElement(e : number){ // this.processor.processElement(e); // } }
5.4 규칙 : 구현체가 하나뿐인 인터페이스를 만들지 말것
- 정의
- 구현체가 하나뿐인 인터페이스를 사용하지 마십시오
- 이유
- 가독성에 도움이 안됨
- 구현클래스 수정하는 경우 인터페이스도 수정해야해서 오버헤드 발생
- 두배 많은 파일로서 상당한 오버헤드 초래
- 스멜
- 추상화는 인지된 복잡성의 감소를 위해, 실제의 복잡성의 증가를 허용하는 것이다 - 존 카맥
- 의도
- 불필요한 코드의 상용구(boilerplate)를 제한하는 것임
- 정의
5.4 리팩터링 패턴 : 구현에서 인터페이스 추출
설명
- 인터페이스 만들기를 필요할 때(e.g. 변형 도입하고 싶을 때)까지 유예할 수 있어서 유용함
class BatchProcessor{ // private processor: ElementProcesssor; constructor(private processor : ElementProcesssor){ // this.processor = new SumProcesssor(accumulator); //인터페이스 대신 클래스를 인스턴스화 } process(arr : number[]){ for(let i = 0 ; i < arr.length ; i++) this.processor.processElement(arr[i]); return this.processor.getAaccumulator(); //접근자 사용해서 필드 획득 } // processElement(e : number){ // this.processor.processElement(e); // } } interface ElementProcesssor{ processElement(e : number): void; getAaccumulator() : number; } class SumProcesssor implements ElementProcesssor{ constructor(private accumulator : number){ } processElement(e : number){ this.accumulator += e; } getAaccumulator(){ return this.accumulator; } } class MiniumProcessor implements ElementProcesssor{ constructor(private accumulator : number){ } processElement(e : number){ if(this.accumulator > e) this.accumulator = e; } getAaccumulator(){ return this.accumulator; } }
요약
- 유사 클래스 통합하기란?
- 모아야 할 코드가 있을 때 통합해야함 → ‘유사클래스 통합’, ‘if문 결합’을 사용해서 클래스를 통합하고, ‘전략패턴의 도입’으로 메서드 통합알 수있음
- 조건부 산술로 구조 노출하기란?
- 연산법칙 (교환법칙 등 이용)
- 간단한 UML 클래스 다이어그램 이해하기란?
- 코드베이스에 대한 특정 아키텍처 변경을 설명하기 위한것으로 사용함
- ‘전략 패턴 도입’으로 유사코드 도입하기란?
- 1번
- ‘구현체가 하나뿐인 인터페이스 만들지 말것’으로 어지러움 정리하기
- 불필요한 인터페이스의 형태임 → 대신 구현에서 인터페이스 추출 리팩터링 패턴을 사용해 나중에 도입해야함
나눔
구현체 하나뿐인 인터페이스 만들지 마는것 굳이?
인터페이스는 사용구의 일반적인 원입니다.
'cleanCode > FiveLinesOfCode' 카테고리의 다른 글
[FiveLinesOfCode] 6장: 유사한 코드 융합하기 (1) | 2023.10.29 |
---|---|
[FiveLinesOfCode] 4장 타입코드 처리하기 (1) | 2023.10.08 |
[FiveLinesOfCode] 3장 : 긴 코드 조각내기 (1) | 2023.10.03 |
[FiveLinesOfCode] 2장 : 리팩터링 깊게 들여다보기 (0) | 2023.10.03 |
[FiveLinesOfCode] 1장 : 리팩터링, 리팩터링 하기 (0) | 2023.10.03 |