Skip to content
Merged
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
387 changes: 387 additions & 0 deletions 챕터_7/오혜성.md
Original file line number Diff line number Diff line change
Expand Up @@ -440,5 +440,392 @@ class Circle extends Shape {
}
```

## 플라이웨이트 패턴

```ts
// 플라이웨이트 패턴 예시 - 폰트 스타일 공유하기

// 공유할 폰트 스타일 (내부 상태)
interface FontStyle {
name: string;
size: number;
color: string;
}

// 플라이웨이트 팩토리
class FontStyleFactory {
private static fontStyles: { [key: string]: FontStyle } = {};

static getFontStyle(name: string, size: number, color: string): FontStyle {
// 캐시 키 생성
const key = `${name}-${size}-${color}`;

// 이미 존재하는 스타일이면 재사용
if (!this.fontStyles[key]) {
console.log('새로운 폰트 스타일 생성:', key);
this.fontStyles[key] = { name, size, color };
}

return this.fontStyles[key];
}
}

// 텍스트 클래스 (외부 상태)
class Text {
private content: string;
private style: FontStyle;

constructor(content: string, name: string, size: number, color: string) {
this.content = content;
// 플라이웨이트 패턴 사용 - 스타일 공유
this.style = FontStyleFactory.getFontStyle(name, size, color);
}

render(): void {
console.log(`텍스트: ${this.content}`);
console.log(`폰트: ${this.style.name}, 크기: ${this.style.size}, 색상: ${this.style.color}`);
}
}

// 사용 예시
const text1 = new Text("안녕하세요!", "Arial", 12, "black");
const text2 = new Text("반갑습니다!", "Arial", 12, "black"); // 동일한 스타일은 재사용됨
const text3 = new Text("좋은 하루!", "Arial", 14, "blue"); // 새로운 스타일 생성

text1.render();
text2.render();
text3.render();

```

- 경량화를 위한 패턴

> 이벤트 위임과 비교해서 설명하는데, 잘 공감되지는 않았음
> 이라 썻다가 쓰면서
> 이벤트 위임을 경량화 관점에서 볼 수도 있겠지?
> 라고 생각이 들었는데, 이걸 말하고 싶었던 것일라나 ;

## 행위 패턴

- 객체 간의 의사소통을 돕는 패턴
- 관찰자
- 중재자
- 커맨드

## 관찰자 패턴

```ts
// 관찰자 인터페이스
interface Observer {
update(message: string): void;
}

// 주체(Subject) 클래스
class NewsAgency {
private observers: Observer[] = [];
private news: string = "";

// 관찰자 등록
subscribe(observer: Observer): void {
this.observers.push(observer);
}

// 관찰자 제거
unsubscribe(observer: Observer): void {
this.observers = this.observers.filter(obs => obs !== observer);
}

// 모든 관찰자에게 알림
notifyObservers(): void {
this.observers.forEach(observer => observer.update(this.news));
}

// 새로운 뉴스 설정
setNews(news: string): void {
this.news = news;
this.notifyObservers();
}
}

// 구체적인 관찰자 클래스
class NewsSubscriber implements Observer {
private name: string;

constructor(name: string) {
this.name = name;
}

update(news: string): void {
console.log(`${this.name}님이 새로운 뉴스를 받았습니다: ${news}`);
}
}

// 사용 예시
const newsAgency = new NewsAgency();
const subscriber1 = new NewsSubscriber("홍길동");
const subscriber2 = new NewsSubscriber("김철수");

newsAgency.subscribe(subscriber1);
newsAgency.subscribe(subscriber2);

newsAgency.setNews("오늘은 날씨가 맑습니다!");
// 출력:
// 홍길동님이 새로운 뉴스를 받았습니다: 오늘은 날씨가 맑습니다!
// 김철수님이 새로운 뉴스를 받았습니다: 오늘은 날씨가 맑습니다!

```

- 그냥 옵저버 걸고, 걸어진 객체들한테 다 쏘는 ㅇㅇ

## 발행/구독 패턴

- 관찰자 패턴에 더해서 토픽/이벤트 채널을 둔 형태
- 특화된 이벤트를 정의할 수 있고
- 구독자에게 필요한 값이 포함된 커스텀 인자를 전달할 수도 있고
- 결국 '발행자와 구독자를 각자 독립적으로 유지'
- 느슨한 결합

```js
const events = () => {
const topics = {};
const hOP = topics.hasOwnProperty;

// ...
if (!hOP.call(topics, topic)) {}
// 이렇게 하는 이유가 있을까?
if (topics.hasOwnProperty(topic)) {}
// 이거랑 다른게 뭐지?
}

// 그래서 제 친구한테 물어봄

// 1. 안정성
const topics = {
hasOwnProperty: () => {}
}
// 이런 경우가 있을 수 있어서 ㅋ 라네요

// 2. 성능 최적화
// hOP 형태처럼 할당해두면 프로토타입 체인 탐색을 줄일 수 있어서 ㅇㅇ

// 근데 난 선호되지 않는 형태 같음
```

```ts
// 발행/구독 패턴 예시
class EventEmitter {
private subscribers: { [key: string]: Function[] } = {};

// 구독하기
subscribe(eventName: string, callback: Function) {
if (!this.subscribers[eventName]) {
this.subscribers[eventName] = [];
}
this.subscribers[eventName].push(callback);

// 구독 취소 함수 반환
return () => {
this.subscribers[eventName] = this.subscribers[eventName].filter(
cb => cb !== callback
);
};
}

// 이벤트 발행하기
publish(eventName: string, data?: any) {
if (!this.subscribers[eventName]) {
return;
}

this.subscribers[eventName].forEach(callback => callback(data));
}
}

// 사용 예시
const emitter = new EventEmitter();

// 날씨 이벤트 구독
const unsubscribeWeather = emitter.subscribe('weather', (data) => {
console.log(`날씨 업데이트: ${data}`);
});

// 뉴스 이벤트 구독
const unsubscribeNews = emitter.subscribe('news', (data) => {
console.log(`뉴스 업데이트: ${data}`);
});

// 이벤트 발행
emitter.publish('weather', '맑음'); // 출력: 날씨 업데이트: 맑음
emitter.publish('news', '새로운 소식입니다!'); // 출력: 뉴스 업데이트: 새로운 소식입니다!

// 구독 취소
unsubscribeWeather();
emitter.publish('weather', '비'); // 아무것도 출력되지 않음
```

- 애플리케이션을 더 작고 느슨하게 연결된 부분으로 나눌 수 있음
- 결과적으로 코드의 관리와 재사용성을 높임
- 데이터를 어떻게 처리할지는 각 구독자가 정함

- 발행자는 시스템의 연결이 분리된 특성 때문에 구독자 기능이 제대로 동작하지 않아도 이를 알 수 없음
- 구독자들이 서로의 존재에 대해 알 수 없음
- 발행자를 변경하는 데 드는 비용을 파악할 수 없음

- 발행 구독 패턴은 자바스크립트 생태계와 매우 잘 어울리는데, ECMAScript의 구현체가 본질적으로 이벤트 기반이기 때문

> 이 부분을 공감할 정도로 이해가 깊지 않은 거 같음
> 커스텀 이벤트 dispatch 정도밖에 떠오르지 않음

- RxJS 이야기
- 뭐 예제를 찔끔 넣어둬서 먼 말인지
Copy link
Contributor

Choose a reason for hiding this comment

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

RxJS를 예시로 들길래 저도 예전에 보려고 아카이빙 했던 아티클 꺼냈던거 공유드립니다..

https://velog.io/@teo/rxjs#hot-cold


## 중재자 패턴

- 중앙 집중식 통제가 핵심
- 사례로 이벤트 위임을 들 수 있음

- 관찰자, 발행/구독 패턴과의 차이점
- 중재자는 자신이 보유한 정보를 바타으로 각 객체의 메서드 호출 시점과 업데이트의 필요성을 판단
- 이를 통해 워크플로와 프로세스를 캡슐화하고 여러 객체 사이를 조율
- 상호 연관성을 갖는 변화가 존재할 경우 중재자 패턴을 사용하여 구현하는 것이 적합

```ts
// 중재자 패턴 예시 - 채팅방

// 중재자 인터페이스
interface ChatMediator {
sendMessage(message: string, sender: User): void;
addUser(user: User): void;
}

// 구체적인 중재자
class ChatRoom implements ChatMediator {
private users: User[] = [];

addUser(user: User): void {
this.users.push(user);
console.log(`${user.getName()}님이 채팅방에 입장했습니다.`);
}

sendMessage(message: string, sender: User): void {
// 메시지를 보낸 사용자를 제외한 모든 사용자에게 메시지 전달
this.users
.filter(user => user !== sender)
.forEach(user => user.receive(message, sender.getName()));
}
}

// 사용자 클래스
class User {
private name: string;
private mediator: ChatMediator;

constructor(name: string, mediator: ChatMediator) {
this.name = name;
this.mediator = mediator;
this.mediator.addUser(this);
}

getName(): string {
return this.name;
}

send(message: string): void {
console.log(`${this.name}: ${message}`);
this.mediator.sendMessage(message, this);
}

receive(message: string, sender: string): void {
console.log(`${this.name}이(가) ${sender}로부터 메시지를 받음: ${message}`);
}
}

// 사용 예시
const chatRoom = new ChatRoom();
const user1 = new User("철수", chatRoom);
const user2 = new User("영희", chatRoom);
const user3 = new User("민수", chatRoom);

user1.send("안녕하세요!");
user2.send("반갑습니다!");

```

## 커맨드 패턴

- 명령을 실행하는 객체와 명령을 호출하는 객체 간의 결합을 느슨하게 해 구체적인 클래스의 변경에 대한 유연성을 향상
- 기본 원칙이 명령을 내리는 객체와 명령을 실행하는 객체의 책임을 분리하는 것

- 책의 예제가 너무 빈약해 손으로는 이해가 안됨
Copy link
Contributor

Choose a reason for hiding this comment

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

ㅇㅈ합니다


```ts
// 커맨드 인터페이스
interface Command {
execute(): void;
}

// 구체적인 커맨드 클래스들
class LightOnCommand implements Command {
private light: Light;

constructor(light: Light) {
this.light = light;
}

execute(): void {
this.light.turnOn();
}
}

class LightOffCommand implements Command {
private light: Light;

constructor(light: Light) {
this.light = light;
}

execute(): void {
this.light.turnOff();
}
}

// 리시버(Receiver) - 실제 동작을 수행하는 객체
class Light {
turnOn(): void {
console.log("불이 켜졌습니다.");
}

turnOff(): void {
console.log("불이 꺼졌습니다.");
}
}

// 인보커(Invoker) - 커맨드를 실행하는 객체
class RemoteControl {
private command: Command;

setCommand(command: Command): void {
this.command = command;
}

pressButton(): void {
this.command.execute();
}
}

// 사용 예시
const light = new Light();
const lightOn = new LightOnCommand(light);
const lightOff = new LightOffCommand(light);

const remote = new RemoteControl();

// 불 켜기
remote.setCommand(lightOn);
remote.pressButton();

// 불 끄기
remote.setCommand(lightOff);
remote.pressButton();

```
Loading