객체간의 책임할당과 관련
어떻게 동작을 실행할 것인가?
// 전구를 리모컨으로 조작할 때의 경우
interface Command {
exe(): void;
undo(): void;
redo(): void;
}
class Bulb {
turnOn(): void {
console.log("전구 켜짐");
}
turnOff(): void {
console.log("전구 꺼짐");
}
}
class TurnOn implements Command {
bulb: Bulb;
constructor(bulb: Bulb) {
this.bulb = bulb;
}
exe(): void {
this.bulb.turnOn();
}
undo(): void {
this.bulb.turnOff();
}
redo(): void {
this.exe();
}
}
class RemoteControl {
submit(command: Command) {
command.exe();
}
}
const bulb = new Bulb();
const turnOn = new TurnOn(bulb);
const remote = new RemoteControl();
remote.submit(turnOn);
/**
* @desc 라디오 채널을 예시로 한 코드
*/
class RadioStation {
frequency: number;
constructor(frequency: number) {
this.frequency = frequency;
}
getFrequency(): number {
return this.frequency;
}
}
class StationList implements Iterable<RadioStation> {
stations: RadioStation[] = [];
counter = 0;
addStation(station: RadioStation) {
this.stations.push(station);
}
removeStation(station: RadioStation) {
this.stations = this.stations.filter(
(s) => s.frequency != station.frequency
);
}
[Symbol.iterator](): Iterator<RadioStation> {
return {
next: () => {
if (this.counter < this.stations.length) {
return { done: false, value: this.stations[this.counter++] };
} else {
this.counter = 0;
return { done: true, value: null };
}
},
};
}
}
const stationList = new StationList();
stationList.addStation(new RadioStation(100));
stationList.addStation(new RadioStation(200));
stationList.addStation(new RadioStation(300));
for (const station of stationList) {
console.log(station.getFrequency());
}
stationList.removeStation(new RadioStation(200));
for (const station of stationList) {
console.log(station.getFrequency());
}
/**
* @desc 채팅방을 예시로 한 중재자 패턴
*/
interface ChatRoomMediator {
showMessage(user: User, message: string): void;
}
class User {
name: string;
chatMediator: ChatRoomMediator;
constructor(name: string, chatMediator: ChatRoomMediator) {
this.name = name;
this.chatMediator = chatMediator;
}
getName() {
return this.name;
}
send(message: string): void {
this.chatMediator.showMessage(this, message);
}
}
class ChatRoom implements ChatRoomMediator {
showMessage(user: User, message: string): void {
const time = new Date().toLocaleDateString();
const sender = user.getName();
console.log(`${time} [${sender}]: ${message}`);
}
}
const chatRoom = new ChatRoom();
const johnUser = new User("John", chatRoom);
const janeUser = new User("Jane", chatRoom);
johnUser.send("hi");
janeUser.send("hey there");
/**
* @desc 텍스트편집기의 예시로 메멘토 패턴
*/
class EditorMemento {
protected content: string;
constructor(content: string) {
this.content = content;
}
getContent(): string {
return this.content;
}
}
class Editor {
protected content = "";
type(words: string) {
this.content = this.content + " " + words;
}
save(): EditorMemento {
return new EditorMemento(this.content);
}
restore(editorMemento: EditorMemento) {
this.content = editorMemento.getContent();
}
print(): void {
console.log(this.content);
}
}
const editor = new Editor();
editor.type("hi");
editor.type("hello");
const editorMemento = editor.save();
editor.type("whatup");
editor.print();
editor.restore(editorMemento);
editor.print();
/**
* @desc 구인구직을 예시로 한 옵저버 패턴
*/
class JobPost {
protected title: string;
constructor(title: string) {
this.title = title;
}
getTitle(): string {
return this.title;
}
}
interface Observer {
onJobPosted(jobPost: JobPost): void;
}
class JobSeeker implements Observer {
protected name: string;
constructor(name: string) {
this.name = name;
}
onJobPosted(jobPost: JobPost): void {
console.log(`Hi ${this.name}! New Job Posted: ${jobPost.getTitle()}`);
}
}
interface Observable {
notify(jobPost: JobPost): void;
attach(observer: Observer): void;
addJob(jobPost: JobPost): void;
}
class EmployeeAgency implements Observable {
protected observers: Observer[] = [];
// 옵저버 등록
attach(observer: Observer): void {
this.observers.push(observer);
}
// 새로운 직업이 등록된다면, 알림 발송
addJob(jobPost: JobPost): void {
this.notify(jobPost);
}
// 등록된 옵저버들에게 새로운 직업이 등록되었다고 알리기
notify(jobPost: JobPost): void {
for (const observer of this.observers) {
observer.onJobPosted(jobPost);
}
}
}
const john = new JobSeeker("john");
const jane = new JobSeeker("jane");
const jobPoster = new EmployeeAgency();
jobPoster.attach(john);
jobPoster.attach(jane);
jobPoster.addJob(new JobPost("FE Engineer"));
/**
* @desc 동물원을 예시로 한 방문자 패턴
* 방문자들에게 여러동물들의 묘기를 보여줘야 함
*/
interface Animal {
accept(operation: AnimalOperation): void;
}
interface AnimalOperation {
visitMonkey(monkey: Monkey): void;
visitLion(lion: Lion): void;
visitDolphin(dolphin: Dolphin): void;
}
class Monkey implements Animal {
accept(operation: AnimalOperation): void {
operation.visitMonkey(this);
}
shout(): void {
console.log("우끼끼");
}
}
class Lion implements Animal {
accept(operation: AnimalOperation): void {
operation.visitLion(this);
}
roar(): void {
console.log("ROAR");
}
}
class Dolphin implements Animal {
accept(operation: AnimalOperation): void {
operation.visitDolphin(this);
}
speak(): void {
console.log("돌고래 울음소리");
}
}
class Speak implements AnimalOperation {
visitMonkey(monkey: Monkey): void {
monkey.shout();
}
visitDolphin(dolphin: Dolphin): void {
dolphin.speak();
}
visitLion(lion: Lion): void {
lion.roar();
}
}
const monkey = new Monkey();
const lion = new Lion();
const dolphin = new Dolphin();
const speak = new Speak();
monkey.accept(speak);
lion.accept(speak);
dolphin.accept(speak);
// 여기서, Jump라는 묘기를 추가한다면 어떡할까?
// 아마 각 동물들을 수정해야 할 것이다.
// 그러지말고, Jump라는 새 방문자를 만들어 보자
class Jump implements AnimalOperation {
visitMonkey(monkey: Monkey): void {
console.log("monkey jump");
}
visitDolphin(dolphin: Dolphin): void {
console.log("dolphin jump");
}
visitLion(lion: Lion): void {
console.log("lion jump");
}
}
const jump = new Jump();
monkey.accept(jump);
lion.accept(jump);
dolphin.accept(jump);
전략 패턴은 상황에 따라 전략을 전환하는 패턴이다.
interface ISort {
sort(dataset: number[]): void;
}
class BubbleSort implements ISort {
sort(dataset: number[]): void {
console.log("데이터의 수가 적을 때는 Bubble이 유용해요!");
}
}
class QuickSort implements ISort {
sort(dataset: number[]): void {
console.log("데이터의 수가 많은 때는 QuickSort가 유용해요!");
}
}
class Sorter {
protected sorterSmall: ISort;
protected sorterBig: ISort;
constructor(sorterSmall: ISort, sorterBig: ISort) {
this.sorterSmall = sorterSmall;
this.sorterBig = sorterBig;
}
sort(dataset: number[]): void {
if (dataset.length > 5) {
return this.sorterBig.sort(dataset);
}
return this.sorterSmall.sort(dataset);
}
}
const dataSet = [1, 2, 3, 4, 5];
const sorter = new Sorter(new BubbleSort(), new QuickSort());
sorter.sort(dataSet);
dataSet.push(6);
sorter.sort(dataSet);
상태가 변경될 때, 클래스의 동작을 변경
객체가 특정상태에 따라 행위를 달리하는 상황에서, 조건문으로 행위를 달리하는 것이 아닌 상태를객체화
하여 상태가 행동할 수 있도록 위임하는 패턴
객체의 생성단계가 절대 변경되지 못할 때, 이 단계를 템플릿화하여 생성하는 패턴이다.
/**
* @desc 프로그램 개발을 예시로 템플릿메소드 패턴
*/
abstract class ProgramBuilder {
public build(): void {
this.test();
this.lint();
this.assemble();
this.deploy();
}
abstract test(): void;
abstract lint(): void;
abstract assemble(): void;
abstract deploy(): void;
}
class AProgramBuilder extends ProgramBuilder {
test(): void {
console.log("AProgramBuilder test");
}
lint(): void {
console.log("AProgramBuilder lint");
}
assemble(): void {
console.log("AProgramBuilder assemble");
}
deploy(): void {
console.log("AProgramBuilder deploy");
}
}
const AProgram = new AProgramBuilder();
AProgram.build();
퍼사드 패턴
builder패턴
템플릿 패턴
과 같이 코드의 복잡성을 낮추려 할 때 많이 사용했던 것 같다.고민의 흔적
이라는 것이라고 느끼며, 디자인 패턴을 활용하는 일은 많지 않지만, 특별한 상황에서의 디자인패턴 활용은 해답이 되어주는 것 같다.아 ~~디자인 패턴 사용하면 될 것 같은데?!
라고 떠올릴 정도로 개념만 익히고 가는 것이 좋다고 생각한다.