From 1f69bfed6a35285bc055591f82b782d928ab6026 Mon Sep 17 00:00:00 2001 From: SangBeom Park Date: Sun, 3 Nov 2024 19:13:47 +0900 Subject: [PATCH 1/2] =?UTF-8?q?7.7=EC=9E=A5=EA=B9=8C=EC=A7=80=20(#42)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../\354\203\201\353\262\224.md" | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 "\354\261\225\355\204\260_7/\354\203\201\353\262\224.md" diff --git "a/\354\261\225\355\204\260_7/\354\203\201\353\262\224.md" "b/\354\261\225\355\204\260_7/\354\203\201\353\262\224.md" new file mode 100644 index 0000000..4d3d5af --- /dev/null +++ "b/\354\261\225\355\204\260_7/\354\203\201\353\262\224.md" @@ -0,0 +1,143 @@ +# 챕터7 자바스크립트 디자인 패턴 + +## 7.1 생성 패턴 +- 생성자 패턴 +- 모듈 패턴 +- 노출 묘듈 패턴 +- 싱글톤 패턴 +- 프로토타입 패턴 +- 팩토리 패턴 + +## 7.2 생성자 패턴 +- es6 출시 이후로 생성자 가진 클래스 만들 수 있게 됨 +- 클래스는 자바스크립트가 가진 프로토타입의 상속을 이용한 문법적 설탕임 +- 새 객체를 초기화하는 constructor() 메서드를 갖고 있어야 됨. new 키워드는 생성자 호출하고 생성자 내부의 this는 새로 생성된 해당 객체 가리킴 + +```js +class Car { + constructor(...) { + ... + } + toString() { + } +} +``` + +생성자 기본 예제인데 여기서 몇가지 문제 있음 +- 상속 어려워짐 +- 생성자로 인스턴스 생성하면 같은 함수 새로 정의하게 됨 + +```js +class Car { + constructor(...) { + ... + } + Car.prototype.toString = function() { + } +} +``` + +이를 해결하기 위해 생성자의 프로토타입에 메서드 추가하면 됨. 그러면 공유함 + +## 7.3 모듈 패턴 +객체 리터럴 => 우리가 흔히 작성하는 방식 + +es10 이전엔 접근 제한자(#)가 없었어서 변수 비공개 개념이 존재하지 않았음 +=> 이걸 함수 스코프를 이용해 비공개 개념을 구현함. 선언된 모듈 내부에서만 변수, 메서드 사용 가능 +=> 근데 반환되는 객체에 포함된 변수, 메서드는 공개되잖아? +=> 반환된 객체에 포함된 변수를 비공개하려면 WeakMap() 사용하면 됨 + +> [예제 링크](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/WeakMap#%EB%B9%84%EA%B3%B5%EA%B0%9C_%EB%A9%A4%EB%B2%84_%ED%9D%89%EB%82%B4%EB%82%B4%EA%B8%B0) + +아래 처럼 모듈 내에서 변수를 내보내지 않고 비공개로 사용 가능 ㄷ ㄷ + +```js +let _counter = new WeakMap(); + +class Module { + constructor() { + // 비공개 카운터 변수 + _counter.set(this, 0); + } + incrementCounter() { + let counter = _counter.get(this); + counter++; + _counter.set(this, counter); + return _counter.get(this); + } + resetCounter() { + console.log(`counter value prior to reset: ${_counter.get(this)}`); + _counter.set(this, 0); + } +} + +const testModule = new Module(); +``` + +믹스인 가져오기 변형, 내보내기 변형.. 시간이 지나면서 각자의 입맛에 맞는 모듈 패턴의 변형들이 등장하기 시작함 + +### 장점 +- 초보 개발자들도 이해하기 쉬움 (중요!) +- 사용하기 쉽고 모듈 사이의 의존성 관리하고 전역 요소를 원하는 만큼만 넘겨주어 유지보수 용이하게 됨 +- 바깥으로 노출 안하면 모듈 내부에 비공개로 유지 + +### 단점 +- 필요한 오류를 고칠 때 복잡도를 높일 수가 있다 + +## 7.4 노출 모듈 패턴 +일만이 행님이 답답해서 만든 패턴 + +비공개 함수와 속성에 접근하는 공개 포인터를 만듦. +이렇게 하면 모듈 가장 아래에 위치한 공개 객체를 더 알아보기 쉬워서 가독성 좋음 + +> 회사에서도 커스텀 훅은 다 노출 모듈 패턴으로 작성함 + + +## 7.5 싱글톤 패턴 +클래스의 인스턴스가 오직 하나만 존재하도록 제한하는 패턴 + +주의할 점은 실근톤이 '객체'나 '클래스'가 아닌 '구조'라는 점. 클로저 변수 변수 자체가 클로저가 아니라 클로저 제공하는 함수 스코프가 클로저를 뜻하는 거처럼.. + +```js +constructor() { + if (this._instance == null) { + if ( isFoo() ) { + this._instance = new FooSingleton(); + } else { + this._instance = new BasicSingleton(); + } + } + return this._instance; +}; +``` + +싱글톤은 지연된 실행이 중요! + +싱글톤을 정적 인스턴스로 구현했다 하더라도, 필요할 때까지는 리소스나 메모리를 소모하지 않도록 지연 생성 될 수 있다고 함 + +리액트에선 싱글톤 대신 context API 나 리덕스 같은 전역 상태 관리 도구 이용하여 개발 함 + +## 7.6 프로토타입 패턴 +이미 존재하는 객체를 복제해 만든 템플릿을 기반으로 새 객체를 생성하는 패턴 + +자바스크립트만이 가진 고유의 방식으로 작업 가능함 + +프로토타입 패턴은 상속 구현하는 쉬운 방법이면서 성능상 이점도 챙기기 가능 +=> 객체 내에 함수를 정의할 때 복사본이 아니라 참조로 생성되어 모든 자식 객체가 동일한 함수 가리키게 할 수 있음 + +## 7.7 팩토리 패턴 + +### 사용하면 좋은 상황 +- 객체나 컴포넌트 생성의 복잡성을 가질 때 +- 다양한 객체를 생성해야 할 때 +- 같은 속성을 공유하는 작은 객체를 다룰 때 +- 덕타이핑 같은 api 규칙만 충족하면 되는 다른 객체 인스턴스와 객체를 구성할 때 + +### 사용하면 안되는 상황 +- 객체 생성 과정이 인터페이스 뒤에 추상화하기 때문에 생성과정이 복잡할 경우 사용하지 마라 + +## 추상 팩토리 패턴 + +같은 목표를 가진 각각 팩토리를 하나의 그룹으로 캡슐화하는 패턴 + +추상 팩토리는 자동차나 트럭같은 차량 타입만 정의, 구체적 팩토리는 차량의 공통 기능을 충족하는 클래스만 구현 From 30ab0d131466b882e1a82eb2cd679cd4a17448d7 Mon Sep 17 00:00:00 2001 From: Orchemi <86189596+Orchemi@users.noreply.github.com> Date: Sun, 3 Nov 2024 23:20:53 +0900 Subject: [PATCH 2/2] =?UTF-8?q?Docs:=20=EB=B0=95=EC=8A=B9=ED=9B=88=207?= =?UTF-8?q?=EC=9E=A5=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../\353\260\225\354\212\271\355\233\210.md" | 295 ++++++++++++++++++ 1 file changed, 295 insertions(+) create mode 100644 "\354\261\225\355\204\260_7/\353\260\225\354\212\271\355\233\210.md" diff --git "a/\354\261\225\355\204\260_7/\353\260\225\354\212\271\355\233\210.md" "b/\354\261\225\355\204\260_7/\353\260\225\354\212\271\355\233\210.md" new file mode 100644 index 0000000..191122f --- /dev/null +++ "b/\354\261\225\355\204\260_7/\353\260\225\354\212\271\355\233\210.md" @@ -0,0 +1,295 @@ +## 생성자 패턴(Constructor Pattern) + +> 객체가 새로 만들어진 뒤 초기화하는 데에 사용되는 특별한 메서드 + + +### 객체 생성 + +자바스크립트에서 새로운 객체를 만들 때는 일반적으로 아래 3가지 방법을 사용한다. + +```js +// 1. 객체 리터럴 +const newObject = {}; + +// 2. Object.create() 메서드 사용 +const newObject = Object.create(Object.prototype); + +// 3. Object 생성자 +const newObject = new Object(); +``` + +### 클래스 + +- ES2015에서 도입 +- 객체 템플릿을 정의하고 캡슐화 및 상속을 구현할 수 있게 해준다. +- 새 객체를 초기화하는 **constructor()** 라는 이름의 메서드를 가지고 있어야 한다. +- **new** 키워드로 생성자를 호출 +- 생성자 내부에서 사용된 **this** 키워드는 새로 생성된 해당 객체(instance) +- 단점 1: 상속이 어렵다. +- 단점 2: 객체 생성시 내부 함수를 매번 새로 정의하는데, 인스턴스들은 모두 동일한 함수를 공유해야 해서 불편이 있다. + + +### 프로토타입 + +- 프로토타입 객체는 함수나 클래스 등 **특정 객체의 모든 인스턴스 내에 공통 메서드를 쉽게 정의**할 수 있게 한다. +- *나의 생각 : prototype 건드는 건 악질 안티패턴이라고 하지 않았나?* + + +## 모듈 패턴(Module Pattern) + +> 프로젝트를 구성하는 코드 단위를 체계적으로 분리 및 관리 + + +### 초기 자바스크립트의 모듈 구현 + +- 객체 리터럴 표기법 +- 모듈 패턴 +- AMD 모듈 +- CommonJS 모듈 + + +### 객체 리터럴 + +- 객체는 중괄호(`{}`) 안에서 키와 값을 쉼표(,)로 구분하여 객체를 정의 +- 오류 방지를 위해 마지막 줄 끝에는 쉼표 사용을 권장하지 않는다. + +
+ +> 나의 생각 + +prettier 같은 곳에서 trailing comma에 대한 여부를 설정할 수 있는데, 오류를 야기할 수 있다면 이런 옵션을 설정하는 기능이 없어야 하는 거 아닌가 하는 생각이 들었어요. + +![trailing commas](/imgs/posts/2024/10/29/1.png) + +그래서 MDN([링크](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Trailing_commas))을 찾아보니 JS 코드에서 새로운 요소를 추가할 때 이전의 마지막 줄을 변경하지 않고 새로운 줄을 바로 추가할 때 유용하게 사용할 수 있다고 나와요. 이렇게 하면 버전 관리가 더 깔끔해지고 코드 편집이 덜 번거로워질 수 있다고 하네요. **JSON을 제외한** 나머지 자바스크립트 코드에서는 허용을 하게 되어있다고 하니, 내용이 조금 충돌하는 면이 있지 않나 싶어요. 오류가 생기는 경우에 대해 알고 계시다면 의견 부탁드릴게요. + + +### 모듈 패턴 + +- 클래스의 캡슐화를 위해 처음 고안 +- 클로저(closure)를 사용하여 '비공개' 상태와 구성을 캡슐화 +- 공개 API만을 노출하고 나머지는 클로저 내부에 비공개로 유지 가능 +- 장점: 다른 app이 사용해야 하는 부분만 노출하고, 핵심 작업은 보호 +- 즉시 실행 함수(IIFE)로 구현 + + +### WeakMap + +- 모듈에서 반환된 객체에 포함된 변수를 비공개할 때 사용 +- 객체만 키로 설정 가능 +- 순회 불가능 +- 모듈 내부의 객체에 접근하는 유일한 방법 : 해당 객체의 참조를 통해서만 가능 + + +### 모듈 패턴의 변형 : 믹스인(mixin) import + +- 유틸 함수나 외부 라이브러리 같은 전역 스코프에 있는 요소를 모듈 내부의 고차 함수에 인자로 전달 +- 전역 스코프 요소를 가져와 마음대로 지정(alias)하여 사용 가능 + + +### 모듈 패턴의 장점 + +- 모듈 패턴은 캡슐화 개념보다 객체 지향 프로그래밍 지식을 가진 초보 개발자가 이해하기 용이 +- 비공개 지원 +- 공개되면 안 되는 코드 캡슐화 가능 +- 여러 의존성을 동시에 사용 가능 +- 이름의 충돌 방지 + + +### 모듈 패턴의 단점 + +- 공개와 비공개 멤버를 서로 다르게 접근해야 한다. +- 공개 여부를 바꾸고 싶다면 값이 위치한 파일로 가서 각각 변경해야 한다. +- 나중에 추가한 메서드에서는 비공개 멤버에 접근할 수 없다. +- 자동화 단위 테스트에서 비공개 멤버는 제외된다. +- 핫픽스가 필요한 오류를 고칠 때 복잡도를 높인다. +- 비공개 멤버는 쉽게 수정하기도 힘들기에 생각만큼 유연하게 사용할 수 없다. +- 모든 JS 런타임에서 ES2015 모듈을 사용하려면 babel 같은 트랜스파일러를 사용해야 한다. + + +### WeakMap을 사용한 모듈 패턴 + +- ES6에서 도입 +- 약한 참조를 가진 키-값의 쌍으로 구성된 집합체 +- 키는 객체, 값은 어떤 것이든 가능 +- 키가 약하게 유지되는 map : 참조되지 않는 키는 가비지 컬렉션의 대상이 된다. + + +## 노출 모듈 패턴 + +- 공개 변수나 메서드에 접근하기 위해 가져온 메인 객체의 이름을 반복 사용해야 하는 불편에서 개선 +- 모든 함수와 변수를 비공개 스코프에 정의 +- 공개하고 싶은 부분만 포인터를 통해 비공개 요소에 접근할 수 있게 하는 익명 객체 반환 + +```js +let privateCounter = 0; + +const privateFunction = () => { + privateCounter++; +}; + +const publicFunction = () => { + publicIncrement(); +}; + +const publicIncrement = () => { + privateFunction(); +}; + +const publicGetCount = () => { + return privateCounter; +}; + + +// 비공개 함수와 속성에 접근하는 공개 포인터 +const myRevealingModule = { + start: publicFunction, + increment: publicIncrement, + count: publicGetCount, +}; + +export default myRevealingModule; + + +// 사용법 +import myRevealingModule from './myRevealingModule.js'; + +myRevealingModule.start(); +``` + + +### 장단점 + +- 장점 + - 코드 일관성 유지 가능 + - 모듈의 가장 아래에 위치한 공개 객체를 더 알아보기 쉽게 바꾸어 가독성 향상 +- 단점 + - 비공개 함수를 참조하는 공개 함수 수정 불가 + - 비공개 변수를 참조하는 공개 객체 멤버 또한 수정 불가 + - 기존 모듈 패턴보다 취약할 수 있다. + + +### 나의 생각 + +메인 객체를 계속 반복해서 사용해야 한다는 점은 같지 않나요...? 기존 모듈 객체 방식이랑 뭐가 크게 차이 나는지는 잘 모르겠어요. + + +## 싱글톤 패턴(Singleton Pattern) + +> 클래스의 인스턴스가 오직 하나만 존재하도록 제한하는 패턴 + +- 전역에서 접근 및 공유해야 하는 단 하나의 객체가 필요할 때 유용 +- 이미 존재하는 인스턴스가 없어야 한다. +- 인스턴스가 이미 존재할 경우 해당 인스턴스의 참조를 반환 +- 정적 클래스나 객체와는 다르게 **초기화를 지연시킬 수 있다.** +- 초기화 시점에 필요한 특정 정보가 유효하지 않을 수도 있기 때문 +- 클래스 내에 공개된 get, set 메서드를 통해 인스턴스를 읽기/수정 가능 + + +### 싱글톤의 특징 + +- 인스턴스에 대한 전역 접근을 허용 +- 클래스의 **인스턴스는 정확히 하나만 있어야 한다.** +- 눈에 잘 보이는 곳에 위치시켜 접근을 용이하게 해야 한다. +- 싱글톤의 인스턴스는 **서브클래싱(sub-classing)**을 통해서만 확장할 수 있어야 한다. +- 코드의 수정 없이 확장된 인스턴스를 사용할 수 있어야 한다. + + +### 지연된 실행? + +> 싱글톤에서는 어째서 지연된 실행이 중요한 걸까? + +- 개발자들에게 제어권을 주어 동적 초기화 순서의 예측 불가능성을 제거하는 역할 +- 싱글톤과 정적 클래스(또는 객체) 사이의 차이점을 명확히 아는 것이 중요 +- **필요할 때까지는 리소스나 메모리를 소모하지 않도록 지연 생성될 수도 있다.** + + +### 주의할 점 + +> JS에서 싱글톤이 필요하다는 것은 설계를 다시 생각해 봐야 한다는 신호일 수도 있다. + +- C++은 객체를 생성하기 위해서 클래스를 정의해야 하지만, JS는 직접 생성할 수 있기 때문 +- 즉, 객체를 하나 생성할 수 있는 상황에서 클래스를 쓰고 있지는 않는지 확인 필요 +- 싱글톤 패턴은 유용하지만 남용되어서는 안 된다.(전역 공간을 차지하기 때문) + + +### 싱글톤의 단점 + +- **싱글톤임을 파악하는 것이 힘들다** : 여러 객체를 인스턴스화 하거나, 부적절하게 수정할 수 있다. +- **테스트하기 힘들다** : 숨겨진 의존성, 의존성 대체의 어려움 등 다양한 문제로 테스트하기 어렵다. + + +### 리액트의 상태관리 + +> 리액트 쓰면 싱글톤 대신 전역 상태 관리 도구를 통해 개발 가능 + +- 전역 상태 관리 도구는 변경 불가능한 읽기 전용 상태 제공 +- 단점을 모두 쉽게 처리할 순 없지만, 적어도 컴포넌트가 전역 상태를 직접 변경할 수 없도록 한다. +- **전역 상태가 의도한 대로 변경될 수 있도록 도와준다.** + + +## 프로토타입 패턴(Prototype Pattern) + +> 이미 존재하는 객체를 복제해 만든 템플릿을 기반으로 새 객체를 생성하는 패턴 - GoF + +- 프로토타입 상속과 클래스는 별개로 사용 + - 클래스 : 따로 정의 + - 프로토타입 : 존재하는 다른 객체를 복제하여 새 객체를 생성 + +### 장점 + +> 다른 언어의 기능을 따라하지 않고, JS만의 고유한 방식으로 작업할 수 있다. + +- *나의 생각 : 사용 언어의 특성을 살리는 것은 좋지만.. JS 구린데 그렇게 해야 하나?* + +
+ +> 상속을 구현하는 쉽고 성능 좋은 방법 + +- 객체 내에 함수를 정의할 때 복사본이 아닌 참조로 생성 +- 모든 자식 객체가 동일한 함수를 가리키게 할 수 있기 때문 + + +### 클래스와 생성자 + +- 클래스와 생성자도 결국 내부적으로는 함수와 프로토타입으로 컴파일 +- 여전히 프로토타입의 장점과 동시에 성능상 이점을 누리고 있는 것 +- *나의 생각 : 그러면 JS 프로토타입의 성능상 이점을 살리기 위해 굳이 prototype을 쓸 필요는 없는 거 아닌가?* + + +## 팩토리 패턴(Factory Pattern) + +> 객체 생성을 위한 인터페이스를 정의하고, 어떤 클래스의 인스턴스를 만들지 서브클래스에서 결정할 수 있게 하는 패턴 + + +### 사용하면 좋은 상황 + +- 객체나 컴포넌트의 생성과정이 복잡할 때 +- 상황에 맞춰 다양한 객체 인스턴스를 편리하게 생성해야 할 때 +- 같은 속성을 공유하는 여러 개의 작은 객체 또는 컴포넌트를 다뤄야 할 때 +- 덕 타이핑처럼 API 규칙만 충족하면 되는 다른 객체의 인스턴스와 함께 객체를 구성할 때 + + +### 사용하면 안 좋은 상황 + +> 잘못된 상황에서 팩토리 패턴을 적용하면 애플리케이션의 복잡도가 크게 증가 + +- 객체 생성 인터페이스 제공이 작업중인 라이브러리나 프레임워크의 설계 목표가 아닐 때 + - 이럴 때는 차라리 위험을 피해 생성자를 사용하는 것이 좋다. +- 객체 생성 과정이 복잡할 경우 + - 단위 테스트의 복잡성이 증가할 수 있다. + - 객체 생성 과정을 인터페이스 뒤에 추상화하기 때문 + + +### 추상 팩토리 패턴(Abstract Factory Pattern) + +> 같은 목푤르 가진 각각의 팩토리들을 하나의 그룹으로 캡슐화 + +- 객체가 어떻게 생성되는지에 대한 세부사항을 알 필요 없이 객체를 사용할 수 있게 한다. +- **객체의 생성 과정에 영향을 받지 않아야 하거나, 여러 타입의 객체로 작업해야 할 때 유용** + + +## 총평 + +전체적으로 JS를 사용한 디자인패턴을 알아봤는데, 피부로 와닿게 사용에 대한 효용이나 필요성을 느끼지는 못한 것 같아요. 뭔가 실무적인 예시가 더 있었다면 이럴 때 쓰면 편하겠구나 체감이 될 것 같은데 간단한 예시 코드에 그치는 것 같아 아쉬웠어요. 제 깊이가 부족한 탓일까요.. 이후엔 조금 더 앞서 소개한 디자인 패턴을 활용하는 실무적인 과정이나 가이드가 있다면 좋겠다고 생각했어요. +