jtwjs Dev Wiki
  • DEV_ROAD
    • 💪🏻 생존하기
    • Week 1
      • 개발 환경 세팅
      • 타입스크립트
      • 리엑트
      • Testing Library
      • Parcel & ESLint
    • Week 2
      • JSX
      • Virtual DOM
    • Week 3
      • React Component
      • React State
    • Week 4
      • Express
      • Fetch API & CORS
      • React Hook
      • useRef & Custom Hook
    • Week 5
      • TDD
      • React Testing Library
      • MSW
      • Playwrite
      • Snapshot
    • Week 6
      • Separtion of Concerns
      • Principle
      • DI, (Dependency Injection)
      • Reflect-metadata
      • TSyringe
      • External Store
      • Follow Redux
      • usestore-ts
      • useSyncExternalStore
    • Week 7
      • Routing
      • Routes
      • Router
      • Navigation
    • Week 8
      • Design System
      • Style Basics
      • CSS-in-JS
      • Styled-Components
      • Global Style & Theme
    • Week 9
      • 개발하기 전 준비
      • 상품 목록 페이지
      • 상품 상세 페이지
      • 장바구니 페이지
    • Week 10
      • 로그인
      • 로그아웃
      • 회원가입
      • 주문 목록 & 주문 상세
    • Week 11
      • 배송 정보 입력
      • 포트원 결제 요청
      • 배송 및 결제 정보 전달
    • Week 12
      • 관리자 웹사이트개발시작
  • DEV_NOTE
    • TypeScript
      • 기본적 문법
        • Enum
        • 다형성
          • Untitled
        • 구조적 타이핑
        • 제너릭 타입
        • 컨디셔널 타입
        • 함수 메서드 타이핑
        • infer로 타입스크립트의 추론 직접 활용
        • 재귀 타입
        • 템플릿 리터럴 타입
        • 추가적인 타입 검사 satisfies 연산자
        • 타입스크립트 건망증
        • 원시 자료형에도 브랜딩 기법 사용 가능
        • 타입 좁히기
        • 유용한 타입 만들기
        • 데코레이터 함수
        • 앰비언트 선언도 선언 병합이 된다.
        • 앰비언트 선언도 선언병합이 된다.
    • Testing
      • Unit Testing
      • 단위 테스트의 두 분파
      • 좋은 단위 테스트를 구성하는 4대 요소
      • 테스트 대역과 식별할 수 있는 동작
      • 단위 테스트 스타일
      • 가치 있는 단위 테스트를 위한 리팩토링
      • 통합 테스트
      • Cross Browsing Testing
      • 기능 테스트 종류
      • React Testing Pattern
      • 프론트엔드 테스트 입문
        • 테스트 범위
        • 단위 테스트 검증
        • Mock
        • UI 컴포넌트 테스트
        • 테스트 커버리지
        • 웹 통합 테스트
        • MSW
        • 스토리북
        • 시각적 회귀 테스트
        • E2E 테스트
        • Github Actions 설정
        • 깃허브 액션에서 E2E
      • 시프트 레프트
        • 테스트 기본중의 기본
        • 단위 테스트
        • 코드 복잡도
        • 리팩터링
        • 코드 리뷰
        • 통합 테스트 패턴
        • 시스템 테스트의 자동화
        • 탐색적 테스트
      • Test Tip
      • vitest
      • playwright
      • Test Data Generator
      • MSW
    • Algorithm
      • coding test
      • Data Structure
    • Next.js
      • Data Fetching
      • Hydration
      • Next 13
      • Optimization
      • Next 15
    • Tailwind
      • Tailwind CSS
      • Theme
      • Directives
      • Tool
      • Design System
    • Storybook
      • Storybook
      • CSF3
      • CDD
      • Headless Component
    • Funtional Programming
      • 함수형 프로그래밍
      • 참조 투명성
      • 부수효과
      • 함수 합성
      • 제너릭 타입 활용하기
      • 암묵적 입출력
      • 액션과 계산, 데이터
      • 계층형 설계
      • 호출 그래프
      • 함수형 설계
      • 불변성
      • 일급 함수
      • 함수형 도구
    • Git
      • Github Actions
      • Conflict
      • Branch 전략
    • Contents Format
      • Audio
    • 3D Graphic
      • 3D keyword
      • Three.js
      • Geometry
      • Material
      • Light
      • Camera
      • Decal
      • Rotation
      • Text
      • Shadow
      • Fog
      • Post Processing
      • Animation
      • Math
        • Vector Space
        • 벡터의 연산
        • 회전 계산
      • 3D 컨텐츠가 만들어지는 과정
      • R3F
      • Env
      • Scene
      • Transform
      • R3F
      • Interaction & Raycast
      • Rendering Algorithnm
      • Blender
      • Blender
    • Accessibility
      • 접근성이란
    • Interactive Web
      • Parallax
      • Canvas
      • requestAnimationFrame
      • Effect
      • HSL
      • React.js + Canvas
      • Matter.js
    • AWS
      • DevOps
      • Amplify
      • S3
      • 클라우드 컴퓨팅
        • 온프레미스와 클라우드
        • 클라우드 도입효과
        • 클라우드 컴퓨팅의 범위
        • 컴퓨팅 옵션
          • EC2 - Virtual Machin
          • ECS, EKS - Container
          • Lambda - Serverless
        • 네트워크 가상화
        • 스토리지
        • 데이터베이스
        • 데이터 수집
        • 머신 러닝 영역
        • IoT 영역
        • 블록체인 영역
      • 클라우드 아키텍처 설계
    • Network
      • Web Server & WAS
    • System Design
      • System Design
      • Component
      • 의존성을 배제한 개발
      • Error Handling
      • Architecture
        • 모노로틱 아키텍처
        • Clean Architecture
        • Layered Architecture
        • 이벤트 기반 아키텍처
      • 상황을 파악하는 메타인지
      • 중복 문제 해결하기
      • Monorepo Arhitecture
        • 모노레포 운영과 트러블슈팅
        • Module Federation
      • 코드 병목지점
      • API 대응
      • 공통 코드
      • Infra 구축
      • 모듈 기반의 개발 방식
      • Design System
        • 최소 수준의 아키텍처 설정
        • 더 효율적인 디자인시스템 만들기
        • 디자인 시스템과 UI 라이브러리 목적
        • 디자인 토큰
      • 효율적인 업무
        • 업무 프로세스 병목 파악
      • Clean Code
      • Design Pattern
        • CQRS Pattern
        • Strangler Fig Pattern
        • 데코레이터 패턴
        • 커맨드 패턴
        • 전략 패턴
        • 옵저버 패턴
      • A/B 테스팅
      • 대규모 리엑트 웹앱 개발
        • 복잡성 관리
        • 모듈성
        • 성능
        • 디자인 시스템
        • 데이터 패칭
        • 상태 관리
        • 국제화
        • 코드 조직화하기
        • 개인화 A/B 테스팅
        • 확장 가능한 웹 아키텍처
        • 테스팅
        • 툴링
        • 기술적 마이그레이션
        • 타입스크립트
        • 라우팅
        • 사용자 중심 API 디자인
        • 리액트 미래
    • Performance
      • React DevTools
      • Component 최적화
      • Page Load
      • API
    • MFA
      • MSA
      • MFA 도입하기
      • Monorepo
        • Monorepo Tool
        • Yarn Berry Workspace
        • Turborepo
      • MFA Composition
      • SPA 통합
      • Design System
      • Package Manager
        • Yarn
        • pnpm
      • Transpiler & Bundler
        • Babel
        • Rollup
        • esbuild
        • swc
        • Webpack
        • Vite
      • 분해와 통합을 위한 여러 기술 비교
    • State Management
      • Zustand
    • React v18
      • Automatic batching
      • Suspense
      • Transition
    • SEO
      • Search Engine Optimization
      • Open Graph Element
      • Metadata
    • FE Develop
      • Scrubbing
      • Clipboard
    • Refactoring
      • 리팩토링 깊게 들여다보기
      • 긴 코드 조각내기
      • 타입 코드 처리하기
      • 유사한 코드 융합하기
      • 데이터 보호
      • 코드 추가 및 제거
    • OAuth 2.0
    • Analytics
      • Mixpanel
    • ETC
      • VSCode
    • React Hook In Action
      • useContext & Provider
      • 커스텀 훅
      • 코드 분할하기 with Suspense, lazy
      • Suspense와 이미지 적재하기
      • useTransition, uesDeferredValue
      • SuspenseList
Powered by GitBook
On this page
  • 인터페이스로 리팩터링 패턴: if 문 결합
  • 복잡한 조건 통합하기
  • UML 클래스 다이어그램 (클래스 관계 묘사)
  • 전략 패턴의 도입
  • 구현체가 하나뿐인 인터페이스를 만들지 말 것
  • 리팩토링 패턴: 구현에서 인터페이스 추출
  • 유사 함수 통합하기
  • 요약하기
  1. DEV_NOTE
  2. Refactoring

유사한 코드 융합하기

인터페이스로 리팩터링 패턴: if 문 결합

내용이 동일한 연속적인 if 문을 결합해서 중복을 제거

  • 내용이 동일한 연속적인 if 문의 코드를 마주하게 되는 일은 드물텐데, 일부 리팩터링 중에 이러한 상태를 의도적으로 만든 사례를 마주하게 된다.

  • 이 패턴은 ||을 추가해서 두 식의 관계를 드러내기 때문에 유용하다.

  • 많은 개발자는 이를 상식이라 생각한다.ㅎㅎ

절차

  1. 본문이 실제로 동일한지 체크

  2. 첫 번째 if 문의 닫는 괄호와 else if 문의 여는 괄호 사이 코드를 선택하고 삭제 후 || 삽입

// before
if (expression1) {
 // 본문
} else if (expression2) {
 // 동일한 본문
}

// after
if ((expression1) || (expression2)) { 
  // 본문
}

복잡한 조건 통합하기

복잡한 조건을 통합하다 보면 결과 조건이 이전보다 더 복잡해진다.

이런 조건을 어떻게 다룰지 얘기해보자.

조건을 위한 산술 규칙 사용

  • || 은 + 더하기 처럼 동작

  • & 은 x 곱하기 처럼 동작

순수 조건 사용

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

  • 조건이란 if 또는 while 뒤에 오는것과 for 루프의 가운데에 있는 것을 의미

  • 순수라는 말은 조건에 부수적인 동작이 없음을 의미

    • 부수적인 동작이란 조건이 변수에 값을 할당하거나 예외를 발생시키거나 I/O와 상호작용하는 것을 의미

Cache

  • 부수저긴 동작에서 반환(return)을 분리할 수 없을 경우 캐시를 사용

  • 캐시를 구현하는 방법으로 여러 가지가 있지만 모든 메서드에 사용가능하고 부수적인 동작을 반환 부분과 분리할 수 있는 범용 캐시를 만들어보자.

class Cache<T> {
  private data: T;
  constructor(private mutator: () => T) {
    this.data = this.mutator()
  }
  
  
  get() {
    return this.data;
  }
  next() {
    this.data = this.mutator();
  }
}
// use case
let tmpBr = new Reader();
let br = new Cacher(() => tmpBr.readLine());
for (; br.get() !== null; br.next()) {
  let line = br.get()
  console.log(line);
}

"메서드는 한 가지 작업을 해야 한다"

부수적인 동작은 한 가지 작업이고 무언가를 반환하는 것은 별개이다.

부수적인 동작을 하거나 무언가를 반환하는 것 중 하나만

조건 산술 적용

  • 먼저 수학 방정식으로 변환한 다음 익숙한 산술 법칙을 사용해서 단순화하고 다시 코드로 변환

  • 조건을 수학 방정식으로 변환하고 단순화한 후 머릿속에서 다시 코드로 바꾸는 과정을 연습하자.

// Before
if (map[y][x].isStony()
        && map[y + 1][x].isAir()
        || map[y][x].isBoxy()
        && map[y + 1][x].isAir())
        
// After
if ((map[y][x].isStony() || map[y][x].isBoxy()) && map[y + 1][x].isAir())

UML 클래스 다이어그램 (클래스 관계 묘사)

class Cls {
  private text: string = 'Hello'
  public name: string;
  private getText() {return this.text;}
  printText() {console.log(this.getText()))
}
  • 대부분의 경우 클래스의 공용 인터페이스의 관심이 있다.

  • 비공개 항목은 일반적으로 포함하지 않는다.

    • 대부분의 필드는 비공개이다.

    • 흔히 공개 메서드만을 묘사하기 때문에 가시성을 신경쓸 필요 없다.

  • 전체 프로그램에 대한 클래스 다이어그램을 만드는것은 순식간에 거대해져 전혀 도움이 되지 않음

  • 주로 디자인 패턴이나 소프트웨어 아키텍처의 작은 부분을 설명하는데 사용하기 때문에 중요한 메서드만 포함됨

클래스 간의 관계

  • 이것을 더 단순화해보자면 "인터페이스에서만 상속받을 것" 규칙 때문에 상속(Inheritance, Generalization) 관계는 사용할 수 없다.

  • 의존(Dependency)과 연관(Association) 화살표는 일반적으로 관계가 무엇인지 모르거나 신경 쓰지 않을 때 사용됨

  • 컴포지션(Composition)과 집합(Aggregation)의 차이는 어떻게 표현하는가의 문제

  • 따라서 대부분의 경우 컴포지션과 구현(Implements, Realization)이라는 두가지 관계 유형을 사용함

구현 (Implements)

interface A {
  m(): void;
}
class B implements A {
  m() { console.log("HEllo") }
}

합성 (Composition)

class A {
  private b: B
}
class B {
}

  • 다른 클래스를 인스턴스화해서 변형(variance)을 도입하는 개념을 전략 패턴이라 한다.

  • 많은 패턴이 전략 패턴의 다른 형태이다.

    • 전략이 필드를 가지고 있는 경우 이를 상태 패턴(state pattern)이라 한다.

    • 이러한 구분은 이론적인것이으로 실제로는 정확한 이름을 아는것이 의사소통에 많은 도움은 되지 않는다.

  • 전략 클래스가 완료된 후 메서드를 추가하는 경우는 거의 없고, 대신 기능을 변경해야 하는 경우 새로운 클래스를 만드는 것을 선호한다.

  • 변형(전략)은 전략 패턴의 목적이기 때문에 항상 상속(Inheritance)으로 묘사된다.

  • 전략 패턴의 변형(variance)은 늦은 바인딩의 궁극적인 형태이다.

    • 런타임에 전략 패턴을 사용하면 코드에 사용자 정의 클래스를 적재하고 이를 제어 흐름에 원활하게 통합할 수 있다.

  • 두 가지 상황에서 전략 패턴을 도입한다.

    • 코드에 변형을 도입하고 싶어서 리팩토링을 수행하는 경우

      • 이 경우 결국 인터페이스가 있어야하는데 리팩터링을 최대한 빠르게하려면 인터페이스는 나중에 만드는 것이좋음

    • 단지 클래스 간의 동작을 통합하려는 경우

      • 구현체가 하나뿐인 인터페이스를 만들지 말것이라는 규칙

      • 인터페이스가 필요할 때 바로 혹은 나중에 구현에서 인터페이스 추출이라는 리팩터링을 사용한다.

절차

  1. 분리하려는 코드에 대해 메서드 추출을 수행 (다른것과 통합하려면 메서드가 동일한지 체크)

  2. 새로운 클래스 만듦

  3. 생성자에서 새로운 클래스를 인스턴스화

  4. 메서드를 새로운 클래스로 옮김

  5. 필드에 종속성이 있을 경우 다음을 수행

    1. 필드를 새로운 클래스로 옮기고 옮긴 필드에 대한 접근자를 만듦

    2. 새로운 접근자를 사용해서 원래 클래스에서 발생하는 오류를 바로잡음

  6. 새로운 클래스의 나머지 오류에 대한 해당 값을 대체할 매개변수를 추가

  7. 메서드의 인라인화를 사용해서 1단계의 추출을 반대로 수행

Example Code
// 초기 코드
class ArrayMinimum {
  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 ArrySum {
  constructor(private accumulator: number) {}
  process(arr: number[]) {
    for(let i=0; i<arr.length; i++) {
      this.accumulator += arr[i];
    }
    return this.accumulator;
  }
}
// After
class ArrayMinimum {
  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.processElement(arr[i]);
    }
    return this.accumulator;
  }
  processElement(e: number) {
    if(this.accumulator > e) {
      this.accumualtor += e;
    }
  }
}
// 신규 클래스 생성
class MinimumProcessor {}
class SumProceessor {}
class ArrayMinimum {
  private processor: MinimumProcessor;
  constructor(private accumulator: number) {
    this.processor = new MinimumProcessor();
  }
  //...
}

class ArraySum {
  private processor: SumProcesesor;
  constructor(private accumulator: number) {
    this.processor = new SumProcessor();
  }
  //...
}
// 메서드를 각각 Minimumprocessor와 SumProcessor로 옮김
class Arrayminimum {
  //...
  processElement(e: number) {
    this.processor.processElement(e);
  }
}

class ArraySum {
  //...
  processElement(e: number) {
    this.processor.processElement(e);
  }
}

class MinimumProcessor {
  processElement(e: number) {
    if (this.accumulator > e) {
      this.accumulator = e;
    }
  }
}

class SumProcessor {
  processElement(e: number) {
    if (this.accumulator > e) {
      this.accumulator += e;
    }
  }
}
// 두 경우 모두 accumulator 필드에 의존하므로 다음 단계를 실행
class Arrayminimum {
  private processor: MinimumProcessor;
  constructor(private accumulator: number) {
    this.processor = new MinimumProcessor(accumulator);
  }
  //...
}

class ArraySum {
  private processor: SumProcessor;
  constructor(private accumulator: number) {
    this.processor = new SumProcessor(accumulator)
  }
}

class MinimumProcessor {
  constructor(private accumulator: number) {}
  getAccumulator() {
    return this.accumulator;
  }
}

class SumProcessor {
  constructor(private accumulator: number) {}
  getAccumulator() {
    return this.accumulator;
  }
}
class ArrayMinimum {
  // ...
  process(arr: number[]) {
    for (let i=0; i<arr.length; i++) {
      this.processElement(arr[i]);
      }
    return this.processor.getAccumulator(); // 접근자를 사용해서 필드 획득
  }
}

class ArraySum {
  // ...
  process(arr: number[]) {
    for (let i=0; i<arr.length; i++) {
      this.processElement(arr[i]);
      }
    return this.processor.getAccumulator(); // 접근자를 사용해서 필드 획득
  }
}
// 메서드의 인라인화를 사용해서 1단계 추출을 반대로
class ArrayMinimum {
  //...
  process(arr: number[]) {
    for (let i=0; i<arr.length; i++) {
      this.processor.processElement(arr[i]); // 인라인화된 processElement 메서드
      }
    return this.processor.getAccumulator(); 
  }
}
/*이 시점에서 두 개의 원래 클래스인 ArrayMinimum과 ArraySum은 
생성자에서의 초기화를 제외하고는 동일하다.
이것은 곧 보게될 "구현에서 인터페이스 추출"을 사용한 후 추출된 클래스를
매개변수로 전달함으로써 해결 가능
*/

class ArrayMinimum {
  private processor: MinimumProcessor;
  constructor(accumulator: number) {
    proceesor = new MinimumProcessor(accumulator);
  }
  process(arr: number[]) {
    for (let i=0; i<arr.length; i++) {
      this.processor.processElement(arr[i]);
    }
    return this.processor.getAccumulator()
  }
}

class MinimumProcessor {
  constructor(private accumulator: number){}
  getAccumulator() {
    return this.accumulator;
  }
  processElement(e) {
    if (this.accumulator > e){
      this.accumulator = e;
    }
  }
}

구현체가 하나뿐인 인터페이스를 만들지 말 것

구현체가 하나뿐인 인터페이스를 사용하지 마라

  • 구현 클래스가 하나밖에 없는 인터페이스는 가독성에 도움이 되지 않는다.

  • 구현 클래스를 수정하려는 경우 인터페이스를 수정해야 해서 오버헤드를 발생시킴

  • 아무런 구현체가 없는 인터페이스를 갖는 것이 합리적인 경우가 있다.

    • 비교자(comparator)와 같은 항목에 대해 익명 클래스를 사용하거나

    • 익명의 내부 클래스를 통해 더 엄격한 캡슐화를 수행하려는 경우 유용

스멜

  • "컴퓨터 과학의 모든 문제는 간접 레이어를 도입함으로써 해결할 수 있다."는 유명한 말이 있다.

    • 이것이 바로 인터페이스이다.

    • 세부적인 내용은 추상화 아래에 숨김

  • "추상화는 인지된 복잡성의 감소를 위해 실제의 복잡성의 증가를 허용하는 것"

    • 추상화에 신중해야 한다는것을 암시

의도

  • 불필요한 코드의 상용구(boilerplate)를 제한하는 것

  • 인터페이스는 상용구의 일반적인 원인

  • 많은 사람이 인터페이스는 항상 바람직하다고 배웠기 때문에 특히 위험하다

  • 그래서 애플리케이션의 크기를 부풀리는 경향이 있음

리팩토링 패턴: 구현에서 인터페이스 추출

인터페이스를 만드는 것을 필요할 때(변형을 도입하고 싶을 때)까지 연기할 수 있어 유용하다

  1. 추출할 클래스와 동일한 이름으로 새로운 인터페이스를 만듬

  2. 인터페이스를 추출할 클래스의 이름을 변경하고 새로운 인터페이스를 구현하게 한다

  3. 컴파일하고 오류를 검토합니다.

    1. new 때문에 오류가 발생하면 인스턴스화하는 부분을 새로운 클래스의 이름으로 변경

    2. 그렇지 않으면 오류를 일으키는 메서드를 인터페이스에 추가한다.

Example Code
// 추출할 클래스와 동일한 이름으로 새 인터페이스 만듬
interface SumProcessor {}
// 인터페이스를 추출하려는 클래스의 이름을 바꾸고 새로운 인터페이스 구현
class TmpName implements SumProcessor {}
// 컴파일 하고 오류 검토
class ArraySum {
  private processor: SumProcessor;
  constructor(accumulator: number) {
    proceesor = new TmpName(accumulator); // 인터페이스 대신 클래스를 인스턴스화
  }
}
// 인터페이스에 메서드 추가
interface SumProcessor {
  processElement(e: number): void;
  getAccumulator(): number;
}

이제 모든 것이 동작하므로 인터페이스 이름을 ElementProcessor 와 같이 더 적합한것으로 변경하고 클래스 이름을 다시 SumProcessor 로 변경해야 한다.

또한 이전의 MinimumProcessor 가 인터페이스를 구현하도록 만든 다음 ArraySum의 accumulator 매개변수를 processor로 바꾸고, 이름을 BatchProcessor로 바꾼다.

따라서 두개의 일괄 처리 프로세서가 동일하며 그 중 하나를 삭제 가능

class BatchProcessor {
  constructor(private prcoessor: ElementProcessor) {}
  process(arr: number[]) {
    for (let i=0; i<arr.length; i++) {
      this.processor.processElement(arr[i]);
    }
    return this.processor.getAccumulator()
  }
}
interface ElementProcessor {
  processElement(e: number): void;
  getAccumulator(): number
}
class MinimumProcessor implements ElementProcessor {
  constructor(private accumulator: number) {}
  getAccumulator() {
    return this.accumulator;
 }
 processElement(e: number) {
   if (this.accumulator > e){
     this.accumulator = e;
   }
 }
}
class SumProcessor implements ElementProcessor {
  constructor(private accumulator: number) {}
  getAccumulator() {
    return this.accumulator;
  }
  processElement(e: number) {
    if(this.accumulator > e){
      this.accumulator += e;
    }
  }
}

유사 함수 통합하기

// removeRock1;
function removeLock1() {
  for (let y=0; y<map.length; y++) {
    for (let x=0; x<map[y].length; x++) {
      if(map[y][x].isLock1()) { // 유일한 차이점
        map[y][x] = new Air();
      }
    }
  }
}

// removeRock2;
function removeLock1() {
  for (let y=0; y<map.length; y++) {
    for (let x=0; x<map[y].length; x++) {
      if(map[y][x].isLock2()) { // 유일한 차이점
        map[y][x] = new Air(); 
      }
    }
  }
}
  • 결론적으로 이것들을 통합하는데 전략 패턴 도입을 활용 가능

  • 두 함수가 동일하지 않으므로 첫 번째 것은 이미 존재하고 두 번째 것을 도입해야 한다는 가정으로 처리

  • 즉 변형을 도입

// 분리하려는 코드에서 메서드 추출을 수행하는것으로 시작
function removeLock1() {
  for (let y=0; y<map.length; y++) {
    for (let x=0; x<map[y].length; x++) {
       if(check(map[y][x])) { // 새로운 메서드 추출 및 호출
         map[y][x] = new Air();
       }
     }
   }
}

function check(tile: Tile) {
  return tile.isLock1();
}
  • 신규 클래스 생성

class RemoveStrategy {}
  • 이 경우 새로운 클래스를 인스턴스화 할 수 있는 생성자가 없다.

  • 대신 함수에서 직접 인스턴스화

function removeLock1() {
  let shouldRemove = new RemoveStrategy(); //새로운 클래스 인스턴스화
  for (let y=0; y<map.length; y++) {
    for (let x=0; x<map[y].length; x++) {
       if(check(map[y][x])) { 
         map[y][x] = new Air();
       }
     }
  }
}
  • 메서드 옮김

function removeLock1() {
  let shouldRemove = new RemoveStrategy(); //새로운 클래스 인스턴스화
  for (let y=0; y<map.length; y++) {
    for (let x=0; x<map[y].length; x++) {
      if (shouldRemove.check(map[y][x]))
        map[y][x] = new Air();
     }
  }
}

class RemoveStrategy {
  check(tile: Tile) {
    return tile.isLock1();
  }
}
  • 전략을 도입했기 때문에 변형을 추가할 수 있도록 구현에서 인터페이스 추출을 사용

// #1.추출할 클래스와 동일한 이름으로 새로운 인터페이스 생성
interface RemoveStrategy {}
// #2. 인터페이스를 추출하려는 클래스의 이름을 바꾸고 새로운 인터페이스를 구현
class RemoveLock1 implements RemoveStrategy {
 //...
}
// #3. 컴파일 후 오류 검토
// #3-1 new로 인한 오류일 경우 새로운 클래스 이름으로 변경
function removeLock1() {
  let shouldRemove = new RemoveLock1(); // 인터페이스 대신 클래스를 인스턴스화
  for (let y=0; y<map.length; y++) {
    for (let x=0; x<map[y].length; x++) {
      if (shouldRemove.check(map[y][x]))
        map[y][x] = new Air();
     }
  }
}
// #3-2 그렇지 않으면 오류를 일으키는 메서드를 인터페이스에 추가
interface RemoveStrategy {
  check(tile: Tile): boolean;
}
  • 이제 RemoveLock1의 복사본에서 RemoveLock2를 만드는것은 간단하다.

  • 만든 다음 shouldRemove만 매개변수로 옮기면 된다.

    • 1. removeLock1에서 첫번째 줄을 제외한 모든 것을 추출해서 remove를 얻는다.

    • 2. shouldRemove 지역변수는 한 번만 사용되므로 인라인화

    • 3. removeLock1에 메서드의 인라인화를 수행해 제거

  • 리팩터링의 결과로 단 하나의 remove만 남게 된다.

function remove(shouldRemove: RemoveStrategy) {
  for (let y=0; y<map.length; y++) {
    for (let x=0; x<map[y].length; x++) {
      if (shouldRemove.check(map[y][x]))
        map[y][x] = new Air();
     }
  }
}

class Key1 implements Tile {
  //...
  moveHorizontal(dx: number) {
    remove(new RemoveLock());
    moveToTile(playerx + dx, playery);
  }
}

interface RemoveStrategy {
  check(tile: Tile): boolean;
}
class RemoveLock1 implements RemoveStrategy {
  check(tile: Tile) {
    return this.isLock1();
  }
}
  • remove() 를 좀 더 일반적으로 만들게됨

  • 또한 추가를 통해 변경을 적용할 수 있는데, 다른 유형의 타일을 제거하려면 수정 없이 RemoveStrategy를 구현하는 다른 클래스를 간단히 만들면 된다.

어떤 애플리케이션에서는 루프 내에서 new를 호출하는 것을 꺼린다.

그렇게하면 애플리케이션의 속도가 느려질 수 있기 때문.. 이 경우 RemoveLock 전략을 인스턴스 변수에 간단히 저장하고 생성자에서 초기화할 수 있다.

요약하기

  • 모아야할 코드가 있을 때 우리는 그것을 통합해야 한다.

    • 유사 클래스 통합, if 문 결합을 사용해서 클래스를 통합하고 전략 패턴의 도입을 사용해서 메서드를 통합할 수 있다.

  • 순수 조건 사용 규칙은 조건에 부수적인 동작이 없어야 한다고 명시한다.

    • 부수적인 동작이 없는 경우 조건부 산술을 사용할 수 있기 때문

    • 또한 부수적인 동작을 조건과 분리하기 위해 캐시를 사용하는 방법도 있음

  • UML 클래스 다이어그램은 일반적으로 코드베이스에 대한 특정 아키텍처의 변경을 설명하기 위해 사용됨

  • 구현 클래스가 하나뿐인 인터페이스는 불필요한 일반화의 한 형태이다.

    • 구현체가 하나뿐인 인터페이스를 만들지 말것 규칙에는 이런 항목이 없어야한다고 명시함

    • 대신 구현에서 인터페이스 추출 리팩터링 패턴을 사용해 인터페이스를 나중에 도입해야 한다.

Previous타입 코드 처리하기Next데이터 보호

Last updated 10 months ago

전략 패턴의 도입
interface로 이미 알수 있기 때문에 클래스 B에도 m 메서드가 있음을 보여줄 필요가 없다