From 359809ed45c0ef73704cfc6cefe22c98645134a1 Mon Sep 17 00:00:00 2001 From: Jiyeon Baek <58380158+100Gyeon@users.noreply.github.com> Date: Tue, 29 Oct 2024 18:59:54 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20=EC=B1=95=ED=84=B0=207=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../\353\260\261\354\247\200\354\227\260.md" | 365 ++++++++++++++++++ 1 file changed, 365 insertions(+) create mode 100644 "\354\261\225\355\204\260_7/\353\260\261\354\247\200\354\227\260.md" diff --git "a/\354\261\225\355\204\260_7/\353\260\261\354\247\200\354\227\260.md" "b/\354\261\225\355\204\260_7/\353\260\261\354\247\200\354\227\260.md" new file mode 100644 index 0000000..f4b1d27 --- /dev/null +++ "b/\354\261\225\355\204\260_7/\353\260\261\354\247\200\354\227\260.md" @@ -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(); +```