핸들러들의 체인(사슬)을 따라 요청을 전달할 수 있게 해주는 행동 디자인 패턴
interface Handler {
setNext(handler: Handler): Handler;
handle(request: string): string;
}
abstract class AbstractHandler implements Handler
{
private nextHandler: Handler;
public setNext(handler: Handler): Handler {
this.nextHandler = handler;
// Returning a handler from here will let us link handlers in a
// convenient way like this:
// monkey.setNext(squirrel).setNext(dog);
return handler;
}
public handle(request: string): string {
if (this.nextHandler) {
return this.nextHandler.handle(request);
}
return null;
}
}
class MonkeyHandler extends AbstractHandler {
public handle(request: string): string {
if (request === 'Banana') {
return `Monkey: I'll eat the ${request}.`;
}
return super.handle(request);
}
}
class SquirrelHandler extends AbstractHandler {
public handle(request: string): string {
if (request === 'Nut') {
return `Squirrel: I'll eat the ${request}.`;
}
return super.handle(request);
}
}
class DogHandler extends AbstractHandler {
public handle(request: string): string {
if (request === 'MeatBall') {
return `Dog: I'll eat the ${request}.`;
}
return super.handle(request);
}
}
function clientCode(handler: Handler) {
const foods = ['Nut', 'Banana', 'Cup of coffee'];
for (const food of foods) {
console.log(`Client: Who wants a ${food}?`);
const result = handler.handle(food);
if (result) {
console.log(` ${result}`);
} else {
console.log(` ${food} was left untouched.`);
}
}
}
const monkey = new MonkeyHandler();
const squirrel = new SquirrelHandler();
const dog = new DogHandler();
monkey.setNext(squirrel).setNext(dog);
적용 할 곳
장점
단점
요청을 요청에 대한 모든 정보가 포함된 독립실행형 객체로 변환하는 행동 디자인 패턴
이 변환은 다양한 요청들이 있는 메서드들을 인수화 할 수 있도록 하며, 요청의 실행을 지연 또는 대기열에 넣을 수 있도록 하고, 또 실행 취소할 수 있는 작업을 지원할 수 있도록 함.
interface Command {
execute(): void;
}
class SimpleCommand implements Command {
private payload: string;
constructor(payload: string) {
this.payload = payload;
}
public execute(): void {
console.log(`SimpleCommand: See, I can do simple things like printing (${this.payload})`);
}
}
class ComplexCommand implements Command {
private receiver: Receiver;
private a: string;
private b: string;
constructor(receiver: Receiver, a: string, b: string) {
this.receiver = receiver;
this.a = a;
this.b = b;
}
public execute(): void {
console.log('ComplexCommand: Complex stuff should be done by a receiver object.');
this.receiver.doSomething(this.a);
this.receiver.doSomethingElse(this.b);
}
}
class Receiver {
public doSomething(a: string): void {
console.log(`Receiver: Working on (${a}.)`);
}
public doSomethingElse(b: string): void {
console.log(`Receiver: Also working on (${b}.)`);
}
}
class Invoker {
private onStart: Command;
private onFinish: Command;
public setOnStart(command: Command): void {
this.onStart = command;
}
public setOnFinish(command: Command): void {
this.onFinish = command;
}
public doSomethingImportant(): void {
console.log('Invoker: Does anybody want something done before I begin?');
if (this.isCommand(this.onStart)) {
this.onStart.execute();
}
console.log('Invoker: ...doing something really important...');
console.log('Invoker: Does anybody want something done after I finish?');
if (this.isCommand(this.onFinish)) {
this.onFinish.execute();
}
}
private isCommand(object): object is Command {
return object.execute !== undefined;
}
}
const invoker = new Invoker();
invoker.setOnStart(new SimpleCommand('Say Hi!'));
const receiver = new Receiver();
invoker.setOnFinish(new ComplexCommand(receiver, 'Send email', 'Save report'));
invoker.doSomethingImportant()
적용 할 곳
장점
단점
interface Iterator<T> {
current(): T;
next(): T;
key(): number;
valid(): boolean;
rewind(): void;
}
interface Aggregator {
getIterator(): Iterator<string>;
}
class AlphabeticalOrderIterator implements Iterator<string> {
private collection: WordsCollection;
private position: number = 0;
private reverse: boolean = false;
constructor(collection: WordsCollection, reverse: boolean = false) {
this.collection = collection;
this.reverse = reverse;
if (reverse) {
this.position = collection.getCount() - 1;
}
}
public rewind() {
this.position = this.reverse ?
this.collection.getCount() - 1 :
0;
}
public current(): string {
return this.collection.getItems()[this.position];
}
public key(): number {
return this.position;
}
public next(): string {
const item = this.collection.getItems()[this.position];
this.position += this.reverse ? -1 : 1;
return item;
}
public valid(): boolean {
if (this.reverse) {
return this.position >= 0;
}
return this.position < this.collection.getCount();
}
}
class WordsCollection implements Aggregator {
private items: string[] = [];
public getItems(): string[] {
return this.items;
}
public getCount(): number {
return this.items.length;
}
public addItem(item: string): void {
this.items.push(item);
}
public getIterator(): Iterator<string> {
return new AlphabeticalOrderIterator(this);
}
public getReverseIterator(): Iterator<string> {
return new AlphabeticalOrderIterator(this, true);
}
}
const collection = new WordsCollection();
collection.addItem('First');
collection.addItem('Second');
collection.addItem('Third');
const iterator = collection.getIterator();
console.log('Straight traversal:');
while (iterator.valid()) {
console.log(iterator.next());
}
console.log('');
console.log('Reverse traversal:');
const reverseIterator = collection.getReverseIterator();
while (reverseIterator.valid()) {
console.log(reverseIterator.next());
}
객체 간의 혼란스러운 의존 관계들을 줄일 수 있는 행동 디자인 패턴
이 패턴은 객체 간의 직접 통신을 제한하고 중재자 객체를 통해서만 협력하도록 함.
interface Mediator {
notify(sender: object, event: string): void;
}
class ConcreteMediator implements Mediator {
private component1: Component1;
private component2: Component2;
constructor(c1: Component1, c2: Component2) {
this.component1 = c1;
this.component1.setMediator(this);
this.component2 = c2;
this.component2.setMediator(this);
}
public notify(sender: object, event: string): void {
if (event === 'A') {
console.log('Mediator reacts on A and triggers following operations:');
this.component2.doC();
}
if (event === 'D') {
console.log('Mediator reacts on D and triggers following operations:');
this.component1.doB();
this.component2.doC();
}
}
}
class BaseComponent {
protected mediator: Mediator;
constructor(mediator?: Mediator) {
this.mediator = mediator!;
}
public setMediator(mediator: Mediator): void {
this.mediator = mediator;
}
}
class Component1 extends BaseComponent {
public doA(): void {
console.log('Component 1 does A.');
this.mediator.notify(this, 'A');
}
public doB(): void {
console.log('Component 1 does B.');
this.mediator.notify(this, 'B');
}
}
class Component2 extends BaseComponent {
public doC(): void {
console.log('Component 2 does C.');
this.mediator.notify(this, 'C');
}
public doD(): void {
console.log('Component 2 does D.');
this.mediator.notify(this, 'D');
}
}
const c1 = new Component1();
const c2 = new Component2();
const mediator = new ConcreteMediator(c1, c2);
console.log('Client triggers operation A.');
c1.doA();
console.log('');
console.log('Client triggers operation D.');
c2.doD();
적용 할 곳
장점
단점
객체의 구현 세부 사항을 공개하지 않으면서 해당 객체의 이전 상태를 저장하고 복원할 수 있게 해주는 행동 디자인 패턴
class Originator {
private state: string;
constructor(state: string) {
this.state = state;
console.log(`Originator: My initial state is: ${state}`);
}
public doSomething(): void {
console.log('Originator: I\'m doing something important.');
this.state = this.generateRandomString(30);
console.log(`Originator: and my state has changed to: ${this.state}`);
}
private generateRandomString(length: number = 10): string {
const charSet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
return Array
.apply(null, { length })
.map(() => charSet.charAt(Math.floor(Math.random() * charSet.length)))
.join('');
}
public save(): Memento {
return new ConcreteMemento(this.state);
}
public restore(memento: Memento): void {
this.state = memento.getState();
console.log(`Originator: My state has changed to: ${this.state}`);
}
}
interface Memento {
getState(): string;
getName(): string;
getDate(): string;
}
class ConcreteMemento implements Memento {
private state: string;
private date: string;
constructor(state: string) {
this.state = state;
this.date = new Date().toISOString().slice(0, 19).replace('T', ' ');
}
public getState(): string {
return this.state;
}
public getName(): string {
return `${this.date} / (${this.state.substr(0, 9)}...)`;
}
public getDate(): string {
return this.date;
}
}
class Caretaker {
private mementos: Memento[] = [];
private originator: Originator;
constructor(originator: Originator) {
this.originator = originator;
}
public backup(): void {
console.log('\nCaretaker: Saving Originator\'s state...');
this.mementos.push(this.originator.save());
}
public undo(): void {
if (!this.mementos.length) {
return;
}
const memento = this.mementos.pop();
console.log(`Caretaker: Restoring state to: ${memento.getName()}`);
this.originator.restore(memento);
}
public showHistory(): void {
console.log('Caretaker: Here\'s the list of mementos:');
for (const memento of this.mementos) {
console.log(memento.getName());
}
}
}
const originator = new Originator('Super-duper-super-puper-super.');
const caretaker = new Caretaker(originator);
caretaker.backup();
originator.doSomething();
caretaker.backup();
originator.doSomething();
caretaker.backup();
originator.doSomething();
console.log('');
caretaker.showHistory();
console.log('\nClient: Now, let\'s rollback!\n');
caretaker.undo();
console.log('\nClient: Once more!\n');
caretaker.undo();
적용 할 곳
장점
단점
당신이 여러 객체에 자신이 관찰 중인 객체에 발생하는 모든 이벤트에 대하여 알리는 구독 메커니즘을 정의할 수 있도록 하는 행동 디자인 패턴
interface Subject {
attach(observer: Observer): void;
detach(observer: Observer): void;
notify(): void;
}
class ConcreteSubject implements Subject {
public state: number;
private observers: Observer[] = [];
public attach(observer: Observer): void {
const isExist = this.observers.includes(observer);
if (isExist) {
return console.log('Subject: Observer has been attached already.');
}
console.log('Subject: Attached an observer.');
this.observers.push(observer);
}
public detach(observer: Observer): void {
const observerIndex = this.observers.indexOf(observer);
if (observerIndex === -1) {
return console.log('Subject: Nonexistent observer.');
}
this.observers.splice(observerIndex, 1);
console.log('Subject: Detached an observer.');
}
public notify(): void {
console.log('Subject: Notifying observers...');
for (const observer of this.observers) {
observer.update(this);
}
}
public someBusinessLogic(): void {
console.log('\nSubject: I\'m doing something important.');
this.state = Math.floor(Math.random() * (10 + 1));
console.log(`Subject: My state has just changed to: ${this.state}`);
this.notify();
}
}
interface Observer {
// Receive update from subject.
update(subject: Subject): void;
}
class ConcreteObserverA implements Observer {
public update(subject: Subject): void {
if (subject instanceof ConcreteSubject && subject.state < 3) {
console.log('ConcreteObserverA: Reacted to the event.');
}
}
}
class ConcreteObserverB implements Observer {
public update(subject: Subject): void {
if (subject instanceof ConcreteSubject && (subject.state === 0 || subject.state >= 2)) {
console.log('ConcreteObserverB: Reacted to the event.');
}
}
}
const subject = new ConcreteSubject();
const observer1 = new ConcreteObserverA();
subject.attach(observer1);
const observer2 = new ConcreteObserverB();
subject.attach(observer2);
subject.someBusinessLogic();
subject.someBusinessLogic();
subject.detach(observer2);
subject.someBusinessLogic();
적용 할 곳
장점
단점
객체의 내부 상태가 변경될 때 해당 객체가 그의 행동을 변경할 수 있도록 하는 행동 디자인 패턴
객체가 행동을 변경할 때 객체가 클래스를 변경한 것처럼 보일 수 있음.
class Context {
private state: State;
constructor(state: State) {
this.transitionTo(state);
}
public transitionTo(state: State): void {
console.log(`Context: Transition to ${(<any>state).constructor.name}.`);
this.state = state;
this.state.setContext(this);
}
public request1(): void {
this.state.handle1();
}
public request2(): void {
this.state.handle2();
}
}
abstract class State {
protected context: Context;
public setContext(context: Context) {
this.context = context;
}
public abstract handle1(): void;
public abstract handle2(): void;
}
class ConcreteStateA extends State {
public handle1(): void {
console.log('ConcreteStateA handles request1.');
console.log('ConcreteStateA wants to change the state of the context.');
this.context.transitionTo(new ConcreteStateB());
}
public handle2(): void {
console.log('ConcreteStateA handles request2.');
}
}
class ConcreteStateB extends State {
public handle1(): void {
console.log('ConcreteStateB handles request1.');
}
public handle2(): void {
console.log('ConcreteStateB handles request2.');
console.log('ConcreteStateB wants to change the state of the context.');
this.context.transitionTo(new ConcreteStateA());
}
}
const context = new Context(new ConcreteStateA());
context.request1();
context.request2();
적용 할 곳
장점
단점
알고리즘들의 패밀리를 정의하고, 각 패밀리를 별도의 클래스에 넣은 후 그들의 객체들을 상호교환할 수 있도록 하는 행동 디자인 패턴
class Context {
private strategy: Strategy;
constructor(strategy: Strategy) {
this.strategy = strategy;
}
public setStrategy(strategy: Strategy) {
this.strategy = strategy;
}
public doSomeBusinessLogic(): void {
// ...
console.log('Context: Sorting data using the strategy (not sure how it\'ll do it)');
const result = this.strategy.doAlgorithm(['a', 'b', 'c', 'd', 'e']);
console.log(result.join(','));
// ...
}
}
interface Strategy {
doAlgorithm(data: string[]): string[];
}
class ConcreteStrategyA implements Strategy {
public doAlgorithm(data: string[]): string[] {
return data.sort();
}
}
class ConcreteStrategyB implements Strategy {
public doAlgorithm(data: string[]): string[] {
return data.reverse();
}
}
const context = new Context(new ConcreteStrategyA());
console.log('Client: Strategy is set to normal sorting.');
context.doSomeBusinessLogic();
console.log('');
console.log('Client: Strategy is set to reverse sorting.');
context.setStrategy(new ConcreteStrategyB());
context.doSomeBusinessLogic();
적용 할 곳
장점
단점
부모 클래스에서 알고리즘의 골격을 정의하지만, 해당 알고리즘의 구조를 변경하지 않고 자식 클래스들이 알고리즘의 특정 단계들을 오버라이드(재정의)할 수 있도록 하는 행동 디자인 패턴
abstract class AbstractClass {
public templateMethod(): void {
this.baseOperation1();
this.requiredOperations1();
this.baseOperation2();
this.hook1();
this.requiredOperation2();
this.baseOperation3();
this.hook2();
}
protected baseOperation1(): void {
console.log('AbstractClass says: I am doing the bulk of the work');
}
protected baseOperation2(): void {
console.log('AbstractClass says: But I let subclasses override some operations');
}
protected baseOperation3(): void {
console.log('AbstractClass says: But I am doing the bulk of the work anyway');
}
protected abstract requiredOperations1(): void;
protected abstract requiredOperation2(): void;
protected hook1(): void { }
protected hook2(): void { }
}
class ConcreteClass1 extends AbstractClass {
protected requiredOperations1(): void {
console.log('ConcreteClass1 says: Implemented Operation1');
}
protected requiredOperation2(): void {
console.log('ConcreteClass1 says: Implemented Operation2');
}
}
class ConcreteClass2 extends AbstractClass {
protected requiredOperations1(): void {
console.log('ConcreteClass2 says: Implemented Operation1');
}
protected requiredOperation2(): void {
console.log('ConcreteClass2 says: Implemented Operation2');
}
protected hook1(): void {
console.log('ConcreteClass2 says: Overridden Hook1');
}
}
function clientCode(abstractClass: AbstractClass) {
// ...
abstractClass.templateMethod();
// ...
}
console.log('Same client code can work with different subclasses:');
clientCode(new ConcreteClass1());
console.log('');
console.log('Same client code can work with different subclasses:');
clientCode(new ConcreteClass2());
적용 할 곳
장점
단점
알고리즘들을 그들이 작동하는 객체들로부터 분리할 수 있도록 하는 행동 디자인 패턴
interface Component {
accept(visitor: Visitor): void;
}
class ConcreteComponentA implements Component {
public accept(visitor: Visitor): void {
visitor.visitConcreteComponentA(this);
}
public exclusiveMethodOfConcreteComponentA(): string {
return 'A';
}
}
class ConcreteComponentB implements Component {
public accept(visitor: Visitor): void {
visitor.visitConcreteComponentB(this);
}
public specialMethodOfConcreteComponentB(): string {
return 'B';
}
}
interface Visitor {
visitConcreteComponentA(element: ConcreteComponentA): void;
visitConcreteComponentB(element: ConcreteComponentB): void;
}
class ConcreteVisitor1 implements Visitor {
public visitConcreteComponentA(element: ConcreteComponentA): void {
console.log(`${element.exclusiveMethodOfConcreteComponentA()} + ConcreteVisitor1`);
}
public visitConcreteComponentB(element: ConcreteComponentB): void {
console.log(`${element.specialMethodOfConcreteComponentB()} + ConcreteVisitor1`);
}
}
class ConcreteVisitor2 implements Visitor {
public visitConcreteComponentA(element: ConcreteComponentA): void {
console.log(`${element.exclusiveMethodOfConcreteComponentA()} + ConcreteVisitor2`);
}
public visitConcreteComponentB(element: ConcreteComponentB): void {
console.log(`${element.specialMethodOfConcreteComponentB()} + ConcreteVisitor2`);
}
}
function clientCode(components: Component[], visitor: Visitor) {
// ...
for (const component of components) {
component.accept(visitor);
}
// ...
}
const components = [
new ConcreteComponentA(),
new ConcreteComponentB(),
];
console.log('The client code works with all visitors via the base Visitor interface:');
const visitor1 = new ConcreteVisitor1();
clientCode(components, visitor1);
console.log('');
console.log('It allows the same client code to work with different types of visitors:');
const visitor2 = new ConcreteVisitor2();
clientCode(components, visitor2);
적용 할 곳
장점
단점
참고
https://refactoring.guru/ko/design-patterns/chain-of-responsibility
https://refactoring.guru/ko/design-patterns/command
https://refactoring.guru/ko/design-patterns/iterator
https://refactoring.guru/ko/design-patterns/mediator
https://refactoring.guru/ko/design-patterns/memento
https://refactoring.guru/ko/design-patterns/observer
https://refactoring.guru/ko/design-patterns/state
https://refactoring.guru/ko/design-patterns/strategy