generated from muhandojeon/study-template
-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[백지연] 챕터 7: 자바스크립트 디자인 패턴 (1/3) #46
The head ref may contain hidden characters: "\uCC55\uD1307_1/\uBC31\uC9C0\uC5F0"
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
``` |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
빌더 패턴을 이해하기 좋은 예시네요 👍🏻 감사합니다