generated from muhandojeon/study-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
changwan
committed
Nov 3, 2024
1 parent
582b55c
commit 1d9fdca
Showing
1 changed file
with
367 additions
and
0 deletions.
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,367 @@ | ||
# 7. 자바스크립트 디자인패턴 (1/3) | ||
|
||
|
||
|
||
### 생성패턴 | ||
|
||
* 생성자 패턴 | ||
* 모듈 패턴 | ||
* 노출 모듈 패턴 | ||
* 싱글톤 패턴 | ||
* 프로토타입 패턴 | ||
* 팩토리 패턴 | ||
|
||
|
||
|
||
### 7.2 생성자 패턴 | ||
|
||
|
||
|
||
#### 7.2.1 객체 생성 | ||
|
||
```js | ||
// <= es5 | ||
|
||
const newObject = {} | ||
|
||
Object.defineProperty(newObject, "name", { | ||
value: "Foo", | ||
writable: true, | ||
enumerable: true, | ||
configurable: true | ||
}) | ||
|
||
//이렇게도 할당 가능 | ||
newObject.name = "Bar" | ||
console.log(newObject) | ||
``` | ||
|
||
|
||
|
||
#### 7.2.2 생성자의 기본 특징 | ||
|
||
es6에서 도입된 class를 통해 객체를 생성하고 초기화할 수 있다. | ||
|
||
```js | ||
class Person { | ||
constructor(name) { | ||
this.name = name | ||
} | ||
} | ||
|
||
const bun = new Person("FooBar") | ||
console.log(bun) | ||
``` | ||
|
||
|
||
|
||
### 7.3 모듈 패턴 | ||
|
||
모듈 패턴을 잘 알지 못하고 처음보았지만, es module을 native하게 지원하고 사용할 수 있는 환경에서 유용성이 큰가? 에 대해아직 물음표인 것 같다. | ||
|
||
es module은 그 자체로 모듈로서 동작하는데, 왜 모듈 래퍼를 하나 더 씌우는 건지 궁금하다. | ||
|
||
|
||
|
||
모듈 그 자체로의 장점은 단일 인스턴스를 보장한다는 점인 것 같다. 객체를 만들려면 singleton으로 몸 비틀기를 해야하는 Java에 비해 단일 인스턴스가 필요한 상황이면 모듈을 이용하는 것으로 충분해 보인다. | ||
|
||
|
||
|
||
|
||
|
||
### 7.3.4 WeakMap을 사용하는 최신 모듈 패턴 | ||
|
||
WeakMap을 통한 접근제한 필요할 때가 있을 것 같지만, class의 instance로 관리하는 것과 유사해보인다. | ||
|
||
1. 먼저, 모듈 변수를 이용한 패턴을 살펴보면, 두 가지 문제점을 볼 수 있다. (WeakMap ❌) | ||
|
||
* 모듈 내의 변수를 공유하게 된다. 즉, 인스턴스 간의 독립적인 상태를 갖지 못한다. | ||
* 동일 모듈에서 모듈 변수들에게 자유롭게 접근이 가능하다. | ||
|
||
```js | ||
let counter = 0; | ||
let action; | ||
|
||
class Countdown { | ||
constructor(initialCounter, initialAction) { | ||
counter = initialCounter; // 모듈 스코프의 counter 변경 | ||
action = initialAction; // 모듈 스코프의 action 변경 | ||
} | ||
|
||
dec() { | ||
if (counter < 1) return; | ||
|
||
counter--; // 모듈 스코프의 counter 감소 | ||
|
||
if (counter === 0) { | ||
action(); // 모듈 스코프의 action 호출 | ||
} | ||
} | ||
} | ||
``` | ||
|
||
|
||
|
||
2. 클래스 내의 멤버 변수로 선언할 경우. | ||
|
||
* 자바스크립트는 비공개 필드를 선언할 수 없었다. | ||
|
||
```js | ||
class Countdown { | ||
counter; | ||
action; | ||
|
||
constructor(counter, action) { | ||
this.counter = counter; | ||
this.action = action; | ||
} | ||
|
||
dec() { | ||
if (this.counter < 1) return; | ||
|
||
this.counter--; // 프라이빗 필드 값 감소 | ||
|
||
if (this.counter === 0) { | ||
this.action(); // 프라이빗 필드로 접근 | ||
} | ||
} | ||
} | ||
const c = new Countdown(2, () => console.log('DONE')); | ||
console.log(c.counter); //1 | ||
c.counter = -1; | ||
console.log(c.counter); // -1 | ||
``` | ||
|
||
|
||
|
||
es2019에 추가된 private class member(`#`)가 도입되었다. 사실 private class member는 트랜스파일링 시, weakMap으로 트랜스파일링 된다. | ||
|
||
```js | ||
class A { | ||
#privateFieldA = 1; | ||
} | ||
|
||
// 트랜스파일링 | ||
var _privateFieldA = /*#__PURE__*/new WeakMap(); | ||
|
||
class A { | ||
constructor() { | ||
_privateFieldA.set(this, { | ||
writable: true, | ||
value: 1 | ||
}); | ||
} | ||
} | ||
|
||
``` | ||
|
||
|
||
|
||
|
||
|
||
#### 7.4 노출 모듈 패턴 | ||
|
||
export로 비공개/공개 요소를 지정하는 것보다 포인터 객체를 만드는 것이 어떤 이점이 있는지 잘 모르겠다.. | ||
|
||
|
||
|
||
#### 7.5 싱글톤 패턴 | ||
|
||
Java의 싱글톤 패턴을 자바스크립트에서 사용하면 문제가 있는지 의심해봐야한다. | ||
|
||
|
||
|
||
#### 7.7 팩토리 패턴 | ||
|
||
싱글 팩토리 패턴 | ||
|
||
```js | ||
function createVechicle(data) { | ||
switch(data.type) { | ||
case "car": | ||
return new Car({...}); | ||
case "truck": | ||
return new Truck({...}); | ||
default: | ||
throw new Error("일체하는 type이 없습니다.") | ||
} | ||
} | ||
``` | ||
|
||
|
||
|
||
위의 싱글 팩토리 패턴은 srp, ocp 위반이다. 그래서 나온 것이 추상 팩토리 메서드 패턴이다. | ||
|
||
잘 동의가 안 되는게, 추상 팩토리를 만든다고 하더라도 진입점에서는 결국 분기처리 해주어야 하는 것이 아닌가? 결국 if,else 분기문의 위치만 달라진다고 느껴진다. | ||
|
||
그러면, 인터페이스 복잡도, 추상도만 올라가는 것 같은데.. 어떤 이점이 있는지 잘 모르겠다. 유용함을 깨달으면 수정해놓겠지. | ||
|
||
|
||
|
||
### 7.8 구조 패턴 | ||
|
||
구조 패턴은 클래스와 객체를 체계적으로 구성하는 방법에 관한 것 | ||
|
||
|
||
|
||
### 7.10 퍼사드 패턴 | ||
|
||
퍼사드란, 내부의 복잡한 로직을 외부에 편리한 높은 수준의 인터페이스를 제공하는 패턴. | ||
|
||
이름을 적절히 추상화해서 외부에 노출하는 것이 중요하는 것이 중요하다. | ||
|
||
|
||
|
||
### 7.10 믹스인 패턴 | ||
|
||
믹스인은 서브클래스가 쉽게 상속받아 기능을 재사용할 수 있도록 하는 클래스 | ||
|
||
|
||
|
||
### 7.11 서브 클래싱 패턴 | ||
|
||
부모 클래스 생성자를 호출(super())하는 것 | ||
|
||
```js | ||
class SuperHero extends Person { | ||
constructor(firstName, lastName, powers) { | ||
super(firstName, lastName) | ||
this.powers = powers; | ||
} | ||
} | ||
``` | ||
|
||
|
||
|
||
### 7.12 믹스인 | ||
|
||
자바스크립트에서 다중 상속을 지원하지 않기 때문에, mixin 패턴을 통해 상속을 흉내낼 수 있다. | ||
|
||
```js | ||
function LoggingMixin<T extends { new(...args: any[]): {} }>(Base: T) { | ||
return class extends Base { | ||
log(message: string) { | ||
console.log(`[LOG]: ${message}`); | ||
} | ||
}; | ||
} | ||
class Product { | ||
name: string; | ||
price: number; | ||
constructor(name: string, price: number) { | ||
this.name = name; | ||
this.price = price; | ||
} | ||
} | ||
// Mixins을 통해 기능 추가 | ||
const LoggingProduct = LoggingMixin(Product) | ||
const product1 = new LoggingProduct("Laptop", 1500) | ||
product1.log("Hello") | ||
``` | ||
|
||
|
||
|
||
React의 HOC와도 비슷해보인다. | ||
|
||
```js | ||
import React from "react"; | ||
|
||
function withLogging(WrappedComponent) { | ||
return class extends React.Component { | ||
log(message) { | ||
console.log(`[LOG]: ${message}`); | ||
} | ||
|
||
render() { | ||
// HOC가 props를 WrappedComponent에 전달 | ||
return <WrappedComponent {...this.props} log={this.log} />; | ||
} | ||
}; | ||
} | ||
|
||
// 기본 컴포넌트 | ||
function Product({ name, price, log }) { | ||
return ( | ||
<div> | ||
<h1>{name}</h1> | ||
<p>Price: {price}</p> | ||
<button onClick={() => log("Product clicked!")}>Log</button> | ||
</div> | ||
); | ||
} | ||
|
||
// HOC를 통해 기능 추가 | ||
const LoggingProduct = withLogging(Product); | ||
|
||
``` | ||
|
||
|
||
|
||
HOC는 이러한 간단한 예시에서는 적절한 추상화를 제공하고, 문제를 우아하게 해결하는 것처럼 보인다. 하지만, 경험적으로 HOC가 많아질수록, 복잡성과 유지보수를 어렵게 만든다. | ||
|
||
|
||
|
||
### 7.13 데코레이터 패턴 | ||
|
||
객체에 동적으로 기능을 추가, 확장할 수 있는 디자인 패턴 | ||
|
||
상속이 컴파일 타임에 확정되는 데 비해, 데코레이터 패턴은 동적으로 추가할 수 있다. | ||
|
||
|
||
|
||
```js | ||
class Coffee { | ||
getCost() { | ||
return 5; // 기본 커피 가격 | ||
} | ||
|
||
getDescription() { | ||
return "Basic Coffee"; | ||
} | ||
} | ||
|
||
|
||
class MilkDecorator { | ||
constructor(coffee) { | ||
this.coffee = coffee; | ||
} | ||
|
||
getCost() { | ||
return this.coffee.getCost() + 2; // 우유 추가 가격 | ||
} | ||
|
||
getDescription() { | ||
return this.coffee.getDescription() + ", Milk"; | ||
} | ||
} | ||
|
||
|
||
//개발자의 인자부하를 올리지는 않을까? 어떤 클래스의 데코레이터인지 파악하기 어려울 것 같다는 생각도 든다 | ||
const coffee = new Coffee(); | ||
const milkCoffee = new MilkDecorator(coffee); | ||
``` | ||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
### 7.14 의사 클래스 데코레이터 | ||
|
||
`Interface.ensureImplements`까지 하는 것은 투머치.. 타입스크립트를 사용하자 | ||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|