cleanCode/FiveLinesOfCode

[FiveLinesOfCode] 5장 유사한 코드 융합하기

Mo_bi!e 2023. 10. 29. 11:17

5장 유사한 코드 융합하기

의문점

  • 성능 최적화는 프로파일링 도구 사용해서 가이드 해야함 (p.112)
  • 인터페이스는 사용구의 일반적인 원입니다.

학습목표

  1. 유사 클래스 통합하기란?
  2. 조건부 산술로 구조 노출하기란?
  3. 간단한 UML 클래스 다이어그램 이해하기란?
  4. 전략 패턴 도입’으로 유사코드 도입하기란?
  5. ‘구현체가 하나뿐인 인터페이스 만들지 말것’으로 어지러움 정리하기

키워드

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)란 메서드가 상수를 반환할 때를 의미한다.

      • 이것은 다른값을 반환하는 상수메서드 공유하기 때문에 두개 클래스 합칠 수있음
      • 절차는 분수 더하는 알고리즘과 유사함
        1. 분수 더하는 알고리즘 : 분모 동일화
          클래스 결합단계 : 상수메서드 제외 클래스 모든것 동일시
        2. 분수 더하는 알고리즘 : 실제 더하기
          클래스 결합단계 : 클래스를 실제 합치기
    • 절차

      1. 두개 moveHorizontal을 동일하게 만듬

        1. 본 메소드 주위에 if(true)추가
        2. isFallingStone() === false 로 바꾸기
        3. else 로 FallingStone 과 서로 합치기
      2. 상수 메서드 isFallingStone 만 다름 → falling 필드 도입 및 할당

      3. bool대신(상수) this.falling 필드반환

      4. 문제확인 위해 컴파일

      5. 각 클래스

        1. falling 매개변수로 받기
        2. new Stone(false) 등에 인자전달
      6. 통합중인 클래스(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문을 결합해서 중복 제거함
      • || 을 추가해서 두식의 관계를 드러내기 때문에 유용함
    • 절차

      1. 본문이 동일한지 확인함

      2. 첫번째 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)’으로 부수적인 동작을 하지 않아야한다
  • 규칙 : 순수조건 사용

    • 정의

      • 조건은 항상 순수조건이어야한다

      • 순수란 조건에 부수적인 동작이 없을것

      • 부수적 동작이란

        1. 조건이 변수에 값을 할당허거나
        2. 예외를 발생시키거나
        3. 출력, 파일 쓰기 등과 같이 I/O 와 상호작용 하는것
      • 순수한 조건을 갖는것이 중요한 이유

        1. 부수적인 동작이 존재하는 조건 때문에 앞서 언급 규칙 이용X
        2. 부수적 동작은 조건문에서 흔히 사용안됨 추적해야하는데 더 많은 시간과 노력 투하
      • 부수적인 동작은 책임을 분리시킬 것

      • 캐시?

    • 스멜

      • 명령에서 질의분리 라는 일반적인 스멜에서 비롯됨
      • 스멜은 부족용이 있는 모든것을 의미
      • 질의는 순수한것을 의미
      • 준수하는 방법은 void메소드 에서만 부수적 동작 허용하기
        • 즉 ‘부수적인 동작’을 하거나 ‘무언가를 반환하는 것’중 하나 해야함
      • 일반적인 스멜과 순수조건 사용의 차이점?
        • 정의 보다는 호출에 초점을 맞춤
        • 조건에 초점 맞추기 위해 이 스멜을 순화함
          • 조건 이외에서 질의와 명령 혼합하는것은 리팩터링 결과에 영향안줌
        • ‘메서드는 한가지 작업을 해야한다’에 근거함
    • 의도

      • 데이터를 가져오는 것과 변경하는 것을 분리하기
      • 부수적인 동작은 위험한 전역상태의 일종
    • 조건 산술적용 (저자 : 이 기법으로 버그 발견하고, 결과 예측가능해짐)

      1. 수학 방적식으로 변환

      2. 익숙한 산술법칙을 사용해서 단순화

      3. 다시 코드로 변환

        //변환 전 
        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;
        }
      }

요약

  1. 유사 클래스 통합하기란?
    1. 모아야 할 코드가 있을 때 통합해야함 → ‘유사클래스 통합’, ‘if문 결합’을 사용해서 클래스를 통합하고, ‘전략패턴의 도입’으로 메서드 통합알 수있음
  2. 조건부 산술로 구조 노출하기란?
    1. 연산법칙 (교환법칙 등 이용)
  3. 간단한 UML 클래스 다이어그램 이해하기란?
    1. 코드베이스에 대한 특정 아키텍처 변경을 설명하기 위한것으로 사용함
  4. 전략 패턴 도입’으로 유사코드 도입하기란?
    1. 1번
  5. ‘구현체가 하나뿐인 인터페이스 만들지 말것’으로 어지러움 정리하기
    1. 불필요한 인터페이스의 형태임 → 대신 구현에서 인터페이스 추출 리팩터링 패턴을 사용해 나중에 도입해야함

나눔

  • 구현체 하나뿐인 인터페이스 만들지 마는것 굳이?

  • 인터페이스는 사용구의 일반적인 원입니다.