Skip to content
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: 자바스크립트 디자인 패턴 (3/3) #67

Merged
merged 3 commits into from
Nov 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions 챕터_7/우창완.md
Original file line number Diff line number Diff line change
Expand Up @@ -292,3 +292,81 @@ const milkCoffee = new MilkDecorator(coffee);
### 7.14 의사 클래스 데코레이터

`Interface.ensureImplements`까지 하는 것은 투머치.. 타입스크립트를 사용하자

### 7.16 플라이웨이트 패턴

플라이웨이트 패턴은 반복되고 비효율적으로 데이터를 공유하는 코드를 최적화하는 구조적 해결 방법

OOP + Data Oriented Programming 과 비슷한 느낌, 데이터 응집성에 포커스를 맞춘다.

`checkoutMember`, `dueReturnDate` 는 Book 의 속성? -> 아니다.

OOP 의 메모리 과도한 점유를 `상태`와 `데이터`의 결합도를 낮춘 형태. 결국 중요한 것은 응집도

### 7.17 행위 패턴

행위패턴이란 `객체간의 의사소통`

- 관찰자 패턴
- 중재자 패턴
- 커맨드 패턴

### 7.18 관찰자 패턴

관찰자 패턴은 update를 기반으로 상태를 관리 (Subject, Observer)

주체를 업데이트 할때, 데이터 변경을 감지할 때 `update`를 기반으로 동작한다.

Subject: Observer: 1: N, (N:M이 될수도 있음)

> Subject.action -> Subject.nofity(Observer.update()) 형태로 Observer에게 Subject의 상태 변경을 알린다.

```
Subject: 데이터의 주체

action: notify() 를 호출한다.
addObserver, removeObserver: Observer => void
notify: data => void // Observer.update() 자신을 구독하고 있는 Observer에게 상태변화를 알림.


Observer: Subject의 관찰자.
update: () => T // 상태변화에 따른 특정 액션
```

### 7.18.1 관찰자 패턴과 발행/구독 패턴의 차이점

Pub/sub 패턴과의 차이는 변경 전파 책임

관찰자 패턴의 변경 감지 전파의 책임은 Subject에게 있다.

시그니처를 보았을 때는 Pub/sub 더 유연해보이고, 상태 변화/감지의 책임이 더 잘 나누어진 것으로 보였음

하지만, 대부분의 경우는 Subject -> Observer로 바로 상태변화를 알리는 것이 명료해보이지만, 응집도는 많이 떨어짐

아래 사고를 거쳐서 계층의 필요타당성을 생각해보기

- 두 객체간의 결합도를 낮추는 것이 꼭 필요할 때 (분리가 필요한 계층, 계층적으로 달라야 필요성이 있을 듯)
- 발행/구독 계층이 어떤 문제를 해결해주는가?

예시 중, 사용자와 리뷰의 관계가 결합도를 낮춰야할만큼의 다른 계층인가? -> 아니라고 생각함, 오히려 응집도가 낮아짐(161p)

EventEmitter 도 떠오르는데, 어떤 Event가 emit되면 특정 함수를 호출하는 형태도 pub/sub의 일부와 비슷해보인다.

RxJS 처음 본 인상은 Effect(state) => ui 형태로 사이드이펙트를 잘 제어할 수 있으면, 유지보수하기 좋은 형태를 만들 수 있을 것 같지만,

사이드이펙트 관리에 대한 이해가 부족하면 오히려 독이 있을 것 같다 (debugging, 참조 투명성, descriptive name의 부재 등)

### 7.19 중재자 패턴

중재자 패턴은 하나의 객체가 이벤트 발생 시, 다른 여러 객체들에게 알림을 보낼 수 있는 디자인 패턴 -> 관찰자 패턴가 뭐가 다르지?

### 7.20 커맨드 패턴

명령을 trigger하는 객체와 명령을 execute 하는 객체가 분리되어 있는 패턴

책의 예시는 실질적으로 결합도를 낮추지 못한다. 불필요한 추상화

-> `buyVehicle` 이 `buyCar` 로 변경되면 execute('buyCar')도 함께 변경되어야 한다.
Comment on lines +367 to +369
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

생각지도 못했던 포인트를 캐치하셨네요 👍🏻 예시가 적절하지 않았던거 같아요


결합도 관련해서 재밌게 읽은 글 남깁니다!
https://maxkim-j.github.io/posts/coupling
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋은 아티클 공유 감삼다 👍🏻

227 changes: 227 additions & 0 deletions 챕터_8/우창완.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
# 자바스크립트 MV* 패턴



## 8.1 MVC 패턴

MVC 패턴에서 Model과 View가 Subject, Observer 관계로 결합도를 낮춘 형태

* Model

```js
// Model: 데이터 관리와 비즈니스 로직
class TodoModel {
constructor() {
this.todos = [];
this.observers = [];
}

addTodo = (text) => {
this.todos.push({ id: Date.now(), text, completed: false });
this.notify();
}

addObserver = (observer) => {
this.observers.push(observer);
}

notify = () => {
this.observers.forEach(observer => observer(this.todos));
}
}
```

* Controller

Controller에서 View에 함수를 binding, model에 observer를 추가한다.

```js
// Controller: Model과 View 연결
class TodoController {
constructor(model, view) {
this.model = model;
this.view = view;

// View의 이벤트를 Model과 연결
this.view.bindAddTodo(this.handleAddTodo);
this.model.addObserver(this.view.display);
}

handleAddTodo = (text) => {
this.model.addTodo(text);
}
}
```



* View

```js
// View: UI 표시와 사용자 입력 처리
class TodoView {
constructor() {
this.input = document.createElement("input");
this.button = document.createElement("button");
this.list = document.createElement("ul");
}

display = (todos) => {
this.list.innerHTML = "";
todos.forEach(todo => {
const li = document.createElement("li");
li.textContent = todo.text;
this.list.appendChild(li);
});
}

bindAddTodo = (handler) => {
this.button.addEventListener("click", () => {
if (this.input.value) {
handler(this.input.value);
this.input.value = "";
}
});
}
}
```





## MVP 패턴

MVC 패턴에서 model과 view 가 관찰자 패턴으로 커뮤니케이션 했다면, MVP패턴은 Presenter 계층이 중간에서 조정하는 역할을한다.

* MVC

```
Model ──(Observer 패턴)─→ View
↑ │
└─────── Controller ──────┘
```



* MVP

```
Model ←→ Presenter ←→ View
```



* 계층 별로 테스트 하기가 더 용이

* 개인적으로는 더 명시적이어서 개발 인지부하가 더 적을거 같다.

* 수동적인 VIew 계층 (MVC에서는 데이터 조작에 직접 관여, MVP에서는 관여 여지가 적음)











### MVVM 모델

Mvvm 모델로 오면서, UI 개발자와 서버 개발자가 분리될 수 있었음

코드를 살펴봤을 때는 MVC모델에서 Model의<->View의 관찰자 패턴의 ViewModel(presenter) <-> View의 관찰자 패턴으로 이동한 것 같다.



MVP에서 Model과 View의 분리의 장점을 가져가면서도, 단방향 데이터 흐름을 만들 수 있는 것이 `킥`이다



아래 코드에서 ViewModel이 View를 직접적으로 모르지만, `상태`가 업데이트되었음을 알리고, View에서 render() 를 통해 선언적으로 UI를 표현하는 방식이 react에도 많은 영향을 주지 않았나 싶다.

```js
class TodoViewModel {
private todos: string[] = [];
private subscribers: Array<() => void> = [];

// ... 중요한 로직들

subscribe(callback: () => void): void {
this.subscribers.push(callback);
}

private notifySubscribers(): void {
this.subscribers.forEach(callback => callback());
}
}

// View
class TodoViewMVVM {
private input: HTMLInputElement;
private list: HTMLUListElement;

constructor(private viewModel: TodoViewModel) {
// DOM 설정...

// 이벤트 바인딩
this.input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
const todo = this.input.value;
if (todo) {
this.viewModel.addTodo(todo);
this.input.value = '';
}
}
});

// ViewModel 구독
this.viewModel.subscribe(() => this.render());
}

private render(): void {
const todos = this.viewModel.getTodos();
this.list.innerHTML = '';
todos.forEach((todo, index) => {
const li = document.createElement('li');
li.textContent = todo;
li.addEventListener('click', () => this.viewModel.removeTodo(index));
this.list.appendChild(li);
});
}
}

```































Loading