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
  • 데이터 보호
  • Getter 없이 캡슐화하기
  • getter와 setter 제거하기
  • Push vs Pull Architectures
  • 규칙 적용하기
  • 간단한 데이터 캡슐화하기
  • 리팩터링 패턴: 데이터 캡슐화
  • 순서에 존재하는 불변속성 제거하기
  • 리팩터링 패턴: 순서 강제화
  • 열거형을 제거하는 또 다른 방법
  • 비공개 생성자를 통한 열거
  1. DEV_NOTE
  2. Refactoring

데이터 보호

Previous유사한 코드 융합하기Next코드 추가 및 제거

Last updated 10 months ago

데이터 보호

  • 데이터와 기능에 대한 접근을 제한하는 캡슐화에 초점을 맞춰 불변속성이 지역에만 영향을 주게 만드는데에 집중

  • 캡슐화를 시행하려면 데이터의 노출을 피해야 한다.

    • getter, setter를 사용하지 말것 규칙은 getter와 setter를 통해 간접적으로 비공개 필드를 노출해서는 안된다는 의미

  • 공통 접사를 사용하지 말것 규칙에 따르면 공통 접두사 또는 접미사가 있는 메서드와 변수가 있을 경우 한 클래스에 함께 있어야 한다.

    • 이를 위해 을 사용할 수 있다.

  • 을 사용하면 컴파일러가 실행 순서를 강제화할 수 있도록 해서 순서 불변속성을 제거할 수 있다.

    • 열거형을 처리하는 또 다른 방법은

    • 그러면 열거형과 스위치 문을 추가적으로 제거 가능

Getter 없이 캡슐화하기

getter와 setter 제거하기

  • 부울이 아닌 필드에 setter나 getter를 사용하지 않기

  • 필드를 비공개로 하는 것의 가장 큰 장점은 그렇게 하는 것이 푸시 기반(push-based)의 아키텍처를 장려하기 때문

    • 푸시 기반 아키텍처에서는 가능한 한 데이터에 가깝게 연산을 이관하지만, 풀 기반(pull-based)의 아키텍처에서는 데이터를 가져와 중앙에서 연산을 수행한다.

  • 푸시 기반 아키텍처에서는 데이터를 가져오는 대신 인자로 데이터를 전달한다.

    • 결과적으로 모든 클래스가 자신의 가능을 가지며 코드는 그 효옹에 따라 분산됨

getter는 흔히 private 필드를 다루기 위한 메서드

  • 객체의 필드에 대한 getter가 존재하는 순간 캡슐화를 해제하고 불변속성을 전역적으로 만들게됨

  • 객체를 반환한 후 이를 반환받은 곳에서 이 객체를 더 많은 곳에 전달할 수 있으며 이는 우리가 제어할 수 없게됨

  • 예상하지 못한 방식으로 객체를 수정할 수 있게됨

setter

  • setter를 통한 새로운 데이터 구조를 반환하도록 getter를 수정하는것은 문제가 된다.

  • 그런 다음 수신자 측에서 이 새로운 데이터 구조를 받을 수 있도록 수정해야 한다.

  • 이것은 정확히 우리가 피하고 싶어하는 밀 결합(tight coupling)의 형태이다.

Push vs Pull Architectures

pull-based

class Website {
  constructor (private url: string) {}
  getUrl() {return this.url}
}

class User {
  constructor (private username: string) {}
  getUsername() {return this.username;}
}

class BlogPost {
  constructor (private author: User, private id: string) {}
  getId() { return this.id; }
  getAuthor() { return this.author; }
}

function generatePostLink(website: Website, post: BlogPost) {
  const url = website.getUrl();
  const user = post.getAuthor();
  const name = user.getUsername();
  const postId = post.getId();
  
  return url + name + postId;
}

push-based

class Website {
  constructor (private url: string) {}
  generateLink(name: string, id: string) {
    return this.url + name + id;
  }
}

class User {
  constructor (private username: string) {}
  generateLink(website: Website, id: string) {
    return website.generateLink(this.username, id);
  }
}

class BlogPost {
  constructor (private author: User, private id: string) {}
  generateLink(website: Website) {
    return this.author.genearteLink(website, this.id);
  }
}

function generatePostLink(website: Website, post: BlogPost) {
  return post.generateLink(website);
}

스멜

  • 이 규칙은 흔히 "낯선 사람에게 말하지 말라"로 요약되는 디미터 법칙에서 유래

    • 여기서 낯선 사람이란 우리가 직접 접근할 수는 없지만 참조를 얻을 수 있는 객체를 뜻함

    • 객체지향 언어에서는 이런 일이 가장 일반적으로 getter를 통해 발생하기 때문에 이 규칙이 존재함

의도

  • 참조를 얻을 수 있는 객체와 상호작용할 때의 문제는 객체를 가져오는 방식과 밀 결합되어 있다는 것

  • 우리는 객체 소유자의 내부 구조에 대해 어느정도 알고 있어야 한다.

  • 필드 소유자는 이전 데이터 구조를 획득하는 방법을 계속 지원하지 않는 한 데이터 구조를 변경할 수 없다.

    • 그렇지 않으면 코드가 손상됨

  • 푸시 기반 아키텍처에서는 서비스와 같은 메서드를 노출한다.

    • 이런 메서드의 사용자는 사용하는 메서드 내부구조에 대해 신경쓰지 않아도 된다.

규칙 적용하기

  1. getter 또는 setter가 사용되는 모든 곳에서 오류가 발생하도록 비공개로 설정

  2. 클래스로의 코드 이관으로 오류를 수정

  3. getter 또는 setter는 클래스로의 코드 이관의 일부로 인라인화됨

    1. 따라서 사용하지 않으므로 삭제해서 다른 사람들이 사용하지 않게 설정

// Before
class BlogPost {
  //...
  getAuthor() { return this.author; }
}

// After - getter를 비공개
class BlogPost {
  //...
  private getAuthor() { return this.author; }
}

function generatePostLink(website: Website, post: Blogpost){
  const url = website.url;
  const name = post.getAuthorName();
  const postId = post.getId();
  return url + name + postId
}

class BlogPost {
  //...
  getAuthorName() {return this.author.getUsername();}
}

간단한 데이터 캡슐화하기

코드에는 공통 접두사나 접미사가 있는 메서드나 변수가 없어야 한다.

  • 사용자의 이름의 경우 username, 또는 타이머를 시작할 때의 동작의 경우 startTimer와 같이 해당 컨텍스트를 암시하기 위해 메서드나 변수에 접미사나 접두사를 붙일 때가 많다.

    • 이것은 컨텍스트를 잘 전달하기 위함

  • 코드를 더 읽기 쉽게 만들 순 있지만 여러 요소가 동일한 접사를 가질 때는 그 요소들의 긴밀성을 나타내기도 한다.

  • 이런 구조를 전달하는 더 좋은 방법이 있는데 바로 클래스이다.

  • 클래스를 이용해서 이런 메서드와 변수를 그룹화하는 장점은 외부 인터페이스를 완전하게 제어할 수 있다는 것

    • 도우미 메서드를 숨겨 전역 범위를 오염시키지 않을 수 있다.

  • 가장 중요한것은 데이터를 숨김으로써 해당 불변속성이 클래스 내에서 관리되게 하는 것

  • 그러면 지역 불변속성이 되어 유지보수하기 더 쉬워진다.

// Bad code
function accountDeposit(to: string, amount: number) {
  const accountId = database.find(to);
  database.updateOne(accountId, { $inc: {balance: amount} });
}
function accountTransfer(amount: number, from: string, to: string) {
  accountDeposit(from, -amount);
  accountDeposit(to, amount);
}

// Good
class Account {
  private deposit(to: string, amount: number) {
    const accountId = database.find(to);
    database.updateOne(accountId, { $inc: {balance: amount} });
  }
  
  transfer(amount: number, from: string, to: string) {
    this.deposit(from, -amount);
    this.deposit(to, amount);
  }
}

스멜

  • 이 규칙이 파생된 스멜은 "단일 책임 원칙"이다.

  • 클래스는 단 하나의 책임만 있어야 한다.

의도

  • 단일 책임 원칙(특정 주제의 기능 집합)으로 클래스를 설계하려면 원칙과 개요가 필요

  • 이 규칙은 하위 책임을 식별하는데 도움이 된다.

  • 공통 접사가 암시하는 구조는 해당 메서드와 변수가 공통 접사의 책임을 공유한다는 것을 의미

  • 따라서 이런 메서드는 이 공통의 책임을 전담하는 별도의 클래스에 있어야 한다.

  • 이 규칙은 제품이 커짐에 따라 책임이 발생할 때 어떤 클래스가 책임을 가지는지 파악하는데 도움이 된다.

리팩터링 패턴: 데이터 캡슐화

변수와 메서드를 캡슐화해서 접근할 수 있는 지점을 제한하고 구조를 명확하게 만들 수 있다.

  • 메서드를 캡슐화하면 이름을 단순하게 만들고, 응집력을 더 명확하게 하는데 도움이 된다.

    • 이것은 더 좋은 클래스로 이어진다.

    • 흔히 더 많은 수의 작은 클래스가 도입되는데 이 또한 유익하다.

    • 많은 개발자들은 클래스를 만드는데 너무 소극적이다?

  • 더 많은 위치에서 데이터에 접근할수록 유지보수가 어려워진다.

  • 범위를 제한하면 클래스 내의 메서드만 데이터를 수정할 수 있으므로 이런 메서드들만 그 속성에 영향을 줄 수 있게 된다.

    • 불변속성을 검증해야할 경우 클래스 내부의 코드만 확인하면 된다. (유지보수성 ㅅㅌㅊ)

어떤 상황에선 변수 없이 공통 접사만 존재하는 메서드만 있을순 있는데, 그런 상황에선 이 리팩터링을 사용하는 것이 여전히 타당할 수 있지만, 절차를 수행하기 전에 메서드를 클래스로 이전해야 한다.

절차

  1. 클래스 만듬

  2. 변수를 새로운 클래스로 이동하고 private 접근자 설정

    1. 변수의 이름을 단순한것으로 정하고 변수에 대한 getter, setter를 만든다.

  3. 변수가 더 이상 전역범위에 없기 때문에 컴파일러가 오류를 발생시켜 모든 참조를 찾을 수 있게 해준다.

  4. 오류를 수정한다.

    1. 새 클래스의 인스턴스에 적합한 변수 이름을 선택

    2. 접근을 가상의 변수에 대한 getter 또는 setter로 바꿈

    3. 2개 이상의 다른 메서드에서 오류가 발생한 경우 이전의 변수명을 가진 매개변수를 첫 번째 매개변수로 추가하고 동일한 변수를 첫 번째 인자로 호출하는 쪾에 놓는다.

    4. 한 메서드에서만 오류가 발생할때가지 반복

    5. 변수를 캡슐화했다면 변수가 선언된 지점에서 새로운 클래스를 인스턴스 화한다. 그렇지 않으면 오류가 발생한 메서드에 인스턴스화하는 부분을 만든다.

// Before
let counter = 0;
function incrementCounter() { counter++ }
function main() {
  for (let i=0; i<20; i++) {
    incrementCounter();
    console.log(counter);
  }
}

// After
// 1. 새로운 클래스 생성
class Counter {}
// 2. 변수를 새로운 클래스로 이동 및 private 설정 & getter, setter 추가
class Counter {
  private counter = 0;
  getCounter() {return this.counter};
  setCounter(c: number) {
    this.counter = c;
  }
}
// 3
function incrementCounter() {
  counter.setCounter(counter.getCounter() + 1);
}
function main() {
 for (let i=0; i<20; i++) {
    incrementCounter();
    console.log(counter.getCounter());
  }
}

// 3 - 2개 이상의 메서드가 오류가 있는 경우 case
function incrementCounter(counter: Counter) {
  counter.setCounter(counter.getCounter() + 1);
}
function main() {
 for (let i=0; i<20; i++) {
    let counter = new Counter(); // <- 잘못된 인스턴스 위치
    incrementCounter(counter);
    console.log(counter.getCounter());
  }
}
// 이런 실수하지 않도록 변수를 캡슐화했는지 여부 확인,
// 이 경우 캡슐화했으므로 변수가 있던 지점에서 새로운 클래스를 인스턴스화
class Counter {...}
const counter = new Counter();
// 그런다음 같은 접미사를 가진 incrementCounter를 쉽게 클래스로 이전 가능
  • 필드에 대한 공개적인(public) 접근을 매개변수로 대체하면 결과적으로 필드 없이 메서드를 캡슐화 가능

  • 매개변수로 바꾸면 적합하다고 판단될 경우, 인스턴스화를 필요한 위치로 더 쉽게 이동할 수 있다는 추가적인 이점yes

  • 매개변수 떄문에 클래스를 사용하기 전에 강제로 인스턴스화해야 하므로 클래스를 전역 범위에서 다룰 때 발생할 수 있는 null 참조 오류를 방지할 수 있다.

순서에 존재하는 불변속성 제거하기

  • map이 map.transform 호출로 초기화된다고 한다면, 객체지향 프로그래밍에서 초기화를 위한 다른 메커니즘을 이용하자.

    • 바로 생성자!!

  • 이렇게 하면 다른 메서드보다 먼저 map.transform을 호출해야 하는 불변속성을 제거하는 효과가 있음

  • 무언가가 다른 것보다 먼저 호출되어야 할 때, 그것을 순서 불변속성(sequence invaraint)라고 한다.

    • 생성자를 먼저 호출하지 않는것이 불가능하기 때문에 이 불변속성은 제거 된다.

    • 이것을 순서 강제화 라고 부른다.

class Map {
  constructor() {...}
}
//...
window.onLoad = () => {
  //map.transform(); 호출 제거
  gameLoop(map);
}

리팩터링 패턴: 순서 강제화

  • 가장 멋진 유형의 리팩터링은 컴파일러에게 프로그램이 어떻게 실행되기를 바라는지 가르쳐줄 수 있을 때

    • 그래야 원하는 대로 실행되는지 확인하는데 도움이 된다.

  • 객체지향 언어에서는 생성자가 항상 객체의 메서드보다 먼저 호출되어야 한다.

    • 이 속성을 활용해서 작업이 특정 순서로 발생하게 할 수 있다.

    • 어떤 메서드를 먼저 호출해야하는지 기억할 필요가 없어진다.

    • 순서를 지키지 않는 것이 불가능하기 떄문

  • 생성자를 사용해서 일부 코드가 실행되게 하면 클래스의 인스턴스가 코드가 실행되었다는 증거가 됨

    • 생성자를 성공적으로 실행하지 않고 인스턴스를 얻을수는 없기 때문

function print(str: string) {
  //...str은 대문자로 된 문자열이어야 한다.
  console.log(str);
}

class CapitlizedString {
  private value: string
  constructor(str: string) {
    this.value = capitalize(str);
  }
  
  print() {
    // 불변속성 제거됨
    console.log(this.value);
  }
}
  • 순서 강제화 변환에는 내부와 외부, 두 가지 변형된 버전이 존재

  • 이전 예제는 내부 변형 버전

// 외부 버전
class CapitalizedString {
  private readonly value: string
  constructor(str: string) {
    this.value = capitalize(str);
  }
}

function print(str: CapitalizedString) {
  console.log(str.value);
}

절차

  1. 마지막으로 실행되어야 하는 메서드에 데이터 캡슐화를 적용

  2. 생성자가 첫 번째 메서드를 호출하도록 설정

  3. 두 메서드의 인자가 연결되어 있으면 이러한 인자를 필드로 만들고 메서드에서 제거

// 돈을 받는 사람의 잔고에서 금액을 더하기 전에 
// 항상 보낸 사람의 잔고에서 먼저 금액을 빼야 한다고 생각해보자. 
// 따라서 순서는 금액의 음수 값으로 deposit을 호출한 후 금액의 양수 값으로 deposit을 호출해야 한다.
function deposit(to: string, amount: number) {
  const accountId = database.find(to);
  database.updateOne(accountId, { $inc: { balance: amount }});
}

// 1. 마지막에 실행되어야할 메서드에 데이터 캡슐화 적용
class Transfer {
  deposit(to: string, amount: number) {
    const accountId = database.find(to);
    database.updateOne(accountId, { $inc: { balance: amount }});
  }
}

// 2. 생성자에 첫 번째 메서드를 호출하도록 설정
class Transfer {
  constructor(from: string, amount: number) {
    // 보낸 사람의 잔고에서 먼저 금액을 빼야하므로
    this.deposit(from, -amount);
  }
  
  deposit(to: string, amount: number) {
    const accountId = database.find(to);
    database.updateOne(accountId, { $inc: { balance: amount }});
  }
}
  • 송금자 측 계좌에서 음수의 amount로 deposit이 먼저 호출된다는 것을 보증할수 있게 됨

  • 더 개선할 수 있는 방법으로 amount 인자를 필드로 만들고 deposit 메서드에서 amount를 제거하여 두 amount를 연결할 수 있다.

    • 한 번은 음수의 amount가 필요하기 때문에 도우미 메서드를 도입

class Transfer {
  constructor(from: string, private amount: number) {
    this.depositHelper(from, -this.amount);
  }
  private depositHelper(to: string, amount: number) {
    const accountId = database.find(to);
    database.updateOne(accountId, { $inc: { balance: amount }})
  }
  deposit(to: string) {
    this.depositHelper(to, this.amount);
  }
}
  • 출금 없이 입금될 수 없다는 것을 보증할 수 있게 되었지만 수취인(to)을 인자로 deposit을 호출하는 것을 잊어버리면 돈이 그냥 사라질 수 있게 됨

  • 따라서 입금도 반드시 발생하도록 이 클래스를 다른 클래스로 감쌀수도 있다.

열거형을 제거하는 또 다른 방법

열거형은 메서드를 가질 수 없다.

비공개 생성자를 통한 열거

  • 사용하는 언어가 열거형에 대해 메서드를 지원하지 않는 경우 private 생성자를 사용해서 이를 우회할 수 있는 기법 존재

  • 모든 객체는 생성자를 호출해서 생성해야 한다.

  • 생성자를 private로 만들면 클래스 내부에서만 객체를 생성할 수 있게 된다.

    • 특히 존재하는 인스턴스 수를 제어할 수 있게 됨(싱글톤)

    • 이를 인스턴스를 공개(public) 상수에 넣으면 열거형으로 사용할 수 있다.

enum TShirtSize {
  SMALL,
  MEDIUM,
  LARGE
}
function sizeToString(s: TShirtSize) {
  if (s === TShirtSize.SMALL)
    return "S";
  else if(s === TShirtSize.MEDIUM)
    return "M";
  else if(s === TShirtSize.LARGE)
    return "L";
}

// 비공개 생성자
// 클래스에 코드를 넣을수 있게 됨
class TShirtSize {
  static readonly SMALL = new TSirtSize();
  static readonly MEDIUM = new TSirtSize();
  static readonly LARGE = new TSirtSize();
  private constructor() {}
}
//비공개 생성자 switch는 사용할 수 없게됨, 괜찮음 스위치는 사용하지 않는 규칙이 있으니
function sizeToString(s: TShirtSize) {
  if (s === TShirtSize.SMALL)
    return "S";
  else if(s === TShirtSize.MEDIUM)
    return "M";
  else if(s === TShirtSize.LARGE)
    return "L";
}

interface SizeValue {}
class SmallValue implements SizeValue {}
class MediumValue implements SizeValue {}
class LargeValue implements SizeValue {}
// 네임스페이스나 패키지를 사용해서 이런 이름을 단순화도 가능

class TShirtSize {
  static readonly SMALL = new TSirtSize(new SmallValue());
  static readonly MEDIUM = new TSirtSize(new MediumValue());
  static readonly LARGE = new TSirtSize(new LargeValue());
  private constructor(private value: SizeValue) {}
}

모듈은 자신이 조작하는 객체의 속사정을 몰라야 한다는 것을 의미

push-based architectures
push vs pull architecture
디미터 법칙:
데이터 캡슐화 리팩터링 패턴
클래스를 이용한 순서 강제화 리팩터링 패턴
비공개 생성자가 있는 클래스를 사용하는 것