diff --git "a/\354\261\225\355\204\260_7/\354\230\244\355\230\234\354\204\261.md" "b/\354\261\225\355\204\260_7/\354\230\244\355\230\234\354\204\261.md" index 2207a2d..e586dc7 100644 --- "a/\354\261\225\355\204\260_7/\354\230\244\355\230\234\354\204\261.md" +++ "b/\354\261\225\355\204\260_7/\354\230\244\355\230\234\354\204\261.md" @@ -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 이야기 + - 뭐 예제를 찔끔 넣어둬서 먼 말인지 + +## 중재자 패턴 + +- 중앙 집중식 통제가 핵심 +- 사례로 이벤트 위임을 들 수 있음 + +- 관찰자, 발행/구독 패턴과의 차이점 + - 중재자는 자신이 보유한 정보를 바타으로 각 객체의 메서드 호출 시점과 업데이트의 필요성을 판단 + - 이를 통해 워크플로와 프로세스를 캡슐화하고 여러 객체 사이를 조율 + - 상호 연관성을 갖는 변화가 존재할 경우 중재자 패턴을 사용하여 구현하는 것이 적합 + +```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("반갑습니다!"); + +``` + +## 커맨드 패턴 + +- 명령을 실행하는 객체와 명령을 호출하는 객체 간의 결합을 느슨하게 해 구체적인 클래스의 변경에 대한 유연성을 향상 + - 기본 원칙이 명령을 내리는 객체와 명령을 실행하는 객체의 책임을 분리하는 것 + +- 책의 예제가 너무 빈약해 손으로는 이해가 안됨 + +```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(); +``` \ No newline at end of file