Skip to content

Commit

Permalink
docs: 챕터 7 추가 (#46)
Browse files Browse the repository at this point in the history
  • Loading branch information
100Gyeon authored Oct 30, 2024
1 parent cf91b90 commit 51b0195
Showing 1 changed file with 365 additions and 0 deletions.
365 changes: 365 additions & 0 deletions 챕터_7/백지연.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,365 @@
# CHAPTER 07 자바스크립트 디자인 패턴

# 생성 패턴

생성 패턴은 **객체를 생성하는 방법**을 다룸

생성 패턴의 종류

- [생성자 패턴](#생성자-패턴)
- [모듈 패턴](#모듈-패턴)
- [노출 모듈 패턴](#노출-모듈-패턴)
- [싱글톤 패턴](#싱글톤-패턴)
- [프로토타입 패턴](#프로토타입-패턴)
- [팩토리 패턴](#팩토리-패턴)
- [빌더 패턴](#빌더-패턴)

## 생성자 패턴

생성자 : 객체가 만들어진 뒤 초기화에 사용되는 메서드

프로토타입 객체는 모든 인스턴스 내에 공통 메서드를 쉽게 정의할 수 있게 한다.
생성자를 통해 객체를 생성하면, 생성자의 프로토타입 객체에 속한 속성을 새 객체에서도 활용할 수 있다.

## 모듈 패턴

### 모듈

애플리케이션 아키텍처의 핵심 구성 요소
코드 단위를 체계적으로 분리/관리하는 데 활용

### 객체 리터럴

모듈 패턴의 일부분은 객체 리터럴을 기반으로 구현
중괄호(`{}`) 안에서 key와 value를 쉼표(`,`)로 구분하여 객체를 정의하는 방법

```javascript
const myObjectLiteral = {
variableKey: variableValue,
functionKey() {},
};
```

### 모듈 패턴

**클래스의 캡슐화**를 위해 고안된 패턴

- 공개 API만 노출하고, 나머지는 **클로저** 내부에 비공개로 유지
- **즉시 실행 함수**를 사용해 객체 반환
- 반환된 객체에 포함된 변수를 비공개하려면 `WeakMap()` 사용
- `WeakMap()`은 객체만 키로 설정할 수 있으며 순회 불가능
- 해당 객체의 참조를 통해서만 모듈 내부 객체에 접근 가능

### 모듈 패턴의 변형

- 믹스인 가져오기 변형
- 내보내기 변형

### 모듈 패턴의 장점

- 모듈 사이의 의존성 관리
- 원하는 만큼만 전역 요소를 넘겨 유지보수에 좋음
- 비공개 지원 (export로 노출한 값만 접근 가능)
- 공개되면 안 되는 코드 캡슐화 (여러 의존성 동시에 사용 가능, 이름 충돌 피함)

### 모듈 패턴의 단점

- 공개/비공개 멤버 접근 방식이 다름
- 나중에 추가한 메서드에서는 비공개 멤버에 접근 불가능
- unit test에서 비공개 멤버 제외
- 오류가 발생한 비공개 멤버를 수정하기 위해서 비공개 멤버를 사용하는 모든 공개 메서드를 살펴봐야 해서 핫픽스 대응이 어려움

### WeakMap을 사용하는 최신 모듈 패턴

[WeakMap](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) 객체

- ES6에서 도입
- key-value 쌍으로 이루어진 집합체
- key는 객체여야만 하고, value는 모든 타입 가능
- key가 약하게 유지되는 Map (참조되지 않는 key는 가비지 컬렉션의 대상)

## 노출 모듈 패턴

모든 함수와 변수를 **비공개 스코프**에 정의하고,
공개하고 싶은 부분만 **포인터**를 통해 비공개 요소에 접근할 수 있게 해주는 **익명 객체**를 반환하는 패턴

### 노출 모듈 패턴의 장점

- 코드의 일관성 유지
- 공개 객체를 알아보기 쉽게 바꾸어 가독성 향상

### 노출 모듈 패턴의 단점

- 비공개 함수를 참조하는 공개 함수, 비공개 변수를 참조하는 공개 객체 멤버 수정 불가능
- 기존 모듈 패턴보다 취약

## 싱글톤 패턴

클래스의 **인스턴스가 오직 하나만 존재하도록 제한**하는 패턴

전역에서 접근 및 공유해야 하는 단 하나의 객체가 필요할 때 유용
싱글톤 패턴을 구현하려면 이미 존재하는 인스턴스가 없어야 하고, 있다면 해당 인스턴스의 참조를 반환해야 함
초기화 시점에 필요한 정보가 유효하지 않으면 초기화 지연시킬 수 있음
인스턴스에 대한 전역 접근을 허용함

### 싱글톤 패턴의 적합성

- 클래스의 인스턴스는 하나만 있어야 하며, 잘 보이는 곳에 위치시켜 접근을 용이하게 해야 한다.
- 싱글톤의 인스턴스는 서브클래싱을 통해서만 확장할 수 있어야 하고, 코드의 수정 없이 확장된 인스턴스를 사용할 수 있어야 한다.

### 자바스크립트에서의 싱글톤

자바스크립트에서 싱글톤이 필요하다는 것은 설계를 다시 생각해 봐야 한다는 신호일 수도 있다.

객체를 생성하기 위해 클래스를 정의해야 하는 다른 언어와 다르게, 자바스크립트는 객체를 직접 생성할 수 있다.
= 싱글톤 클래스를 만드는 대신, 직접 객체를 하나 생성할 수 있다.

자바스크립트에서 싱글톤 클래스 사용 시 단점

- 싱글톤임을 파악하기 어려움 (일반 클래스로 착각)
- 테스트하기 어려움 (숨겨진 의존성, 여러 인스턴스 생성의 어려움, 의존성 대체의 어려움 등의 문제)
- 애플리케이션이 점점 커지면 올바른 실행 순서를 보장하기 어려움

### 리액트의 상태 관리

리액트로 개발하면 **싱글톤 대신 Context API나 Redux 같은 전역 상태 관리 도구**를 이용할 수 있다.
싱글톤과 달리, **변경 불가능한 읽기 전용 상태**를 제공한다.

## 프로토타입 패턴

이미 존재하는 객체를 복제해 만든 **템플릿**을 기반으로 새 객체를 생성하는 패턴

프로토타입의 **상속**을 기반으로 함
프로토타입의 상속은 클래스처럼 따로 정의되는 것이 아니라, 이미 존재하는 다른 객체를 **복제**하는 것

프로토타입 역할을 할 전용 객체 생성 → 이 객체는 생성자를 통해 만들어진 객체의 **설계도**
ex. 프로토타입이 name 속성을 가지고 있다면, 해당 생성자 함수를 사용해 만들어진 객체들은 모두 name 속성 가짐

### 프로토타입 패턴의 장점

- 자바스크립트만이 가진 고유한 방식으로 작업할 수 있다.
- 상속을 구현하는 쉬운 방법이다.
- 객체 내의 함수가 복사본이 아닌 참조로 생성되어 성능에서의 이점을 챙길 수 있다.

> 처음부터 객체를 만들고 설정하는 수고를 겪는 대신, 기존 객체의 복사본을 만들고 필요에 따라 수정할 수 있게 해주는 것
> 실생활에 비유한 예시 : 복제 양 돌리
> `Object.create`를 활용한 프로토타입 패턴 예시
```typescript
class Sheep {
protected name: string;
protected category: string;

constructor(name: string, category: string = 'Mountain Sheep') {
this.name = name;
this.category = category;
}

setName(name: string): void {
this.name = name;
}

getName(): string {
return this.name;
}

setCategory(category: string): void {
this.category = category;
}

getCategory(): string {
return this.category;
}
}

const original: Sheep = new Sheep('Jolly');
console.log(original.getName()); // Jolly
console.log(original.getCategory()); // Mountain Sheep

// 필요한 부분을 복제하고 수정
const cloned: Sheep = Object.create(original);
cloned.setName('Dolly');
console.log(cloned.getName()); // Dolly
console.log(cloned.getCategory()); // Mountain Sheep
```

## 팩토리 패턴

다른 패턴과 달리 **생성자를 필요로하지 않음**
팩토리 객체에 어떤 요소가 필요한지 알려주면 **결과물을 인스턴스화**하여 사용할 수 있도록 준비

> 실생활에 비유한 예시 : 집을 짓는 중에 문이 필요하다고 가정해 보세요.
> 집 안에서 목수 옷을 입고 나무, 접착제, 못과 문을 만드는 데 필요한 모든 도구를 가져와 직접 문을 만들 수도 있지만,
> 간단히 공장에 전화하여 만들어진 문을 받아 설치할 수도 있습니다.
> 그렇게 하면 아무것도 배울 필요가 없으면서 문의 제작 과정에서 발생하는 혼란을 다룰 필요가 없습니다.
### 팩토리 패턴의 장점

- 객체나 컴포넌트의 생성 과정이 복잡할 때 유용
- 상황에 맞춰 다양한 객체 인스턴스를 생성해야 할 때 유용
- 같은 속성을 공유하는 여러 개의 작은 객체 또는 컴포넌트를 다뤄야 할 때 유용
- 덕 타이핑 같은 API 규칙만 충족하면 되는 다른 객체의 인스턴스와 함께 객체를 구성할 때 유용

### 팩토리 패턴의 단점

- 객체 생성 인터페이스 제공이 설계 목표가 아닌 경우 적합하지 않음
- 객체 생성 과정을 인터페이스 뒤에 추상화하기 때문에 객체 생성 과정이 복잡하면 단위 테스트의 복잡성도 증가함

### 추상 팩토리 패턴

**같은 목표를 가진 각각의 팩토리들을 하나의 그룹으로 캡슐화**하는 패턴
객체가 어떻게 생성되는지 알 필요 없이 객체를 사용할 수 있게 함
객체의 생성 과정에 영향을 받지 않아야 하거나, 여러 타입의 객체로 작업해야 하는 경우에 적합

> 팩토리들의 팩토리
>
> 실생활에 비유한 예시 : 팩토리에서 문 예시를 확장해보겠습니다.
> 필요에 따라 나무 문 가게에서 나무 문을, 철문 가게에서 철문을, PVC 관련 가게에서 PVC 문을 구할 수 있습니다.
> 또한, 문을 설치하기 위해 다른 전문 기술을 가진 사람이 필요할 수 있습니다.
> 예를 들어 나무 문에는 목수, 철문에는 용접사가 필요합니다.
> 보시다시피 이제 문들 간에 의존성이 존재하며, 나무 문에는 목수가 필요하고 철문에는 용접사가 필요합니다.
```typescript
interface DoorFactory {
makeDoor(): Door;
makeFittingExpert(): DoorFittingExpert;
}

// 나무 문과 목수를 얻을 수 있는 나무 문 팩토리
class WoodenDoorFactory implements DoorFactory {
makeDoor(): Door {
return new WoodenDoor();
}

makeFittingExpert(): DoorFittingExpert {
return new Carpenter();
}
}

// 철문과 용접사를 얻을 수 있는 철문 팩토리
class IronDoorFactory implements DoorFactory {
makeDoor(): Door {
return new IronDoor();
}

makeFittingExpert(): DoorFittingExpert {
return new Welder();
}
}

// Wooden Factory
const woodenFactory: DoorFactory = new WoodenDoorFactory();
const door: Door = woodenFactory.makeDoor();
const expert: DoorFittingExpert = woodenFactory.makeFittingExpert();

door.getDescription(); // 출력: I am a wooden door
expert.getDescription(); // 출력: I can only fit wooden doors

// Iron Factory도 마찬가지
const ironFactory: DoorFactory = new IronDoorFactory();
const door: Door = ironFactory.makeDoor();
const expert: DoorFittingExpert = ironFactory.makeFittingExpert();

door.getDescription(); // 출력: I am an iron door
expert.getDescription(); // 출력: I can only fit iron doors
```

> 나무 문 팩토리는 목수와 나무 문을 캡슐화했으며, 철문 팩토리는 철문과 용접사를 캡슐화했습니다.
> 이를 통해 생성된 각 문에 대해 잘못된 설치 전문가가 배정되지 않도록 도와주었습니다.
## 빌더 패턴

> 챕터 6에는 생성 패턴에 빌더 패턴도 포함된다고 하고 있는데, 챕터 7에는 설명이 없어서 추가해 봤습니다.
> 맥도날드에 가서 특정 메뉴를 주문한다고 상상해 보세요.
> 예를 들어, 상하이 버거를 주문하면 그들은 아무 질문 없이 상하이 버거를 건네줍니다.
>
> 이는 팩토리 패턴의 예시입니다.
> 그러나 생성 로직에 더 많은 단계가 포함될 수 있는 경우도 있습니다.
> 예를 들어, 서브웨이 주문 시에는 여러 가지 옵션(빵, 치즈, 소스)을 선택해야 합니다.
> 이와 같은 경우가 빌더 패턴입니다.
>
> 빌더 패턴은 점층적 생성자 안티 패턴(telescoping constructor anti-pattern)을 해결하기 위해 개발된 객체 생성 패턴입니다.
> 점층적 생성자 안티 패턴이 무엇인지 설명을 조금 추가하겠습니다. 우리는 한 번쯤 아래와 같은 생성자를 보았을 것입니다.
```typescript
constructor(size: string, cheese: boolean = true, pepperoni: boolean = true, tomato: boolean = false, lettuce: boolean = true) {
// 생성자의 내용
}
```

> 보시다시피, 생성자의 매개변수 개수가 금세 많아질 수 있으며, 매개변수의 배열을 이해하기 어려워질 수 있습니다.
> 또한, 이러한 매개변수 목록은 앞으로 더 많은 옵션을 추가하고자 할 경우 계속해서 늘어날 수 있습니다.
> 이를 점층적 생성자(Telescoping Constructor) 안티 패턴이라고 합니다.
>
> 위와 같은 경우에 버거 인스턴스를 생성하는 방법은 아래와 같습니다.
> 옵션을 버거 생성 시마다 명시해야 하므로 필요한 매개변수가 많아지고, 옵션을 빠뜨리기 쉽습니다.
```typescript
const burger = new Burger(5, true, false, true, true);
```

> 반면 빌더 패턴을 적용한 코드는 BurgerBuilder 클래스를 통해 필요한 옵션만 메서드 체이닝으로 설정할 수 있습니다.
```typescript
// 만들고자 하는 버거
class Burger {
protected size: number;
protected cheese: boolean = false;
protected pepperoni: boolean = false;
protected lettuce: boolean = false;
protected tomato: boolean = false;

constructor(builder: BurgerBuilder) {
this.size = builder.size;
this.cheese = builder.cheese;
this.pepperoni = builder.pepperoni;
this.lettuce = builder.lettuce;
this.tomato = builder.tomato;
}
}
```

```typescript
// 빌더
class BurgerBuilder {
public size: number;
public cheese: boolean = false;
public pepperoni: boolean = false;
public lettuce: boolean = false;
public tomato: boolean = false;

constructor(size: number) {
this.size = size;
}

addPepperoni(): BurgerBuilder {
this.pepperoni = true;
return this;
}

addLettuce(): BurgerBuilder {
this.lettuce = true;
return this;
}

addCheese(): BurgerBuilder {
this.cheese = true;
return this;
}

addTomato(): BurgerBuilder {
this.tomato = true;
return this;
}

build(): Burger {
return new Burger(this);
}
}
```

```typescript
// 다음과 같이 사용
const burger = new BurgerBuilder(14).addPepperoni().addLettuce().addTomato().build();
```

0 comments on commit 51b0195

Please sign in to comment.