행위 패턴 (Behavior Patterns)
행위 패턴은 객체나 클래스 간의 책임 할당과 상호작용 방식을 정의합니다. 이는 객체 간의 통신과 협력을 관리하고, 시스템의 유연성을 높이며, 알고리즘과 흐름 제어를 개선하는 데 중요한 역할을 합니다.
OOP(객체 지향 프로그래밍)에서 행위 패턴(Behavioral Pattern)이 중요한 이유
1. 객체 간의 상호작용 관리
- 객체 간의 상호작용을 정의하고 캡슐화하여 코드의 가독성과 유지보수성을 향상시킬 수 있습니다.
- 객체 간의 의사소통을 유연하게 변경할 수 있어, 시스템의 확장성과 유연성을 높일 수 있습니다.
2. 책임 분배와 응집도 향상
- 행위 패턴을 사용하면 책임을 적절히 분배하여 클래스 간의 응집도를 높일 수 있습니다.
- 각각의 클래스가 하나의 책임을 가지도록 하여, 코드의 유지보수성을 높이고 결합도를 낮출 수 있습니다.
3. 알고리즘과 흐름 제어의 유연성 향상
- 행위 패턴을 사용하면 알고리즘을 다양한 방식으로 구현하고 교체할 수 있습니다.
- 코드의 복잡도를 줄이고, 알고리즘의 재사용성과 유연성을 높일 수 있습니다.
4. 객체 간의 결합도 감소
- 행위 패턴을 사용하면 객체 간의 결합도를 낮출 수 있어, 코드 변경 시 영향 범위를 최소화할 수 있습니다.
- 객체 간의 의존성을 줄이고, 유연한 설계를 가능하게 합니다.
행위 패턴 종류
1. Chain of Responsibility (책임 연쇄)
정의: 요청을 처리할 수 있는 기회를 객체 체인에게 넘깁니다.
특징:
- 요청을 처리할 객체를 명시하지 않고, 여러 객체에게 기회를 제공합니다.
- 객체 간의 결합도를 낮출 수 있습니다.
사용 시기:
- 여러 객체가 순차적으로 요청을 처리해야 할 때.
- 요청 처리의 순서를 유연하게 변경하고자 할 때.
예시:
import React from 'react';
// Handler 인터페이스 정의
interface Handler {
setNext: (handler: Handler) => Handler;
handle: (request: string) => string | null;
}
// Concrete Handlers 구현
class ConcreteHandler1 implements Handler {
private nextHandler: Handler | null = null;
setNext(handler: Handler): Handler {
this.nextHandler = handler;
return handler;
}
handle(request: string): string | null {
if (request === 'Request1') {
return 'Handler1 handled the request';
} else if (this.nextHandler) {
return this.nextHandler.handle(request);
}
return null;
}
}
class ConcreteHandler2 implements Handler {
private nextHandler: Handler | null = null;
setNext(handler: Handler): Handler {
this.nextHandler = handler;
return handler;
}
handle(request: string): string | null {
if (request === 'Request2') {
return 'Handler2 handled the request';
} else if (this.nextHandler) {
return this.nextHandler.handle(request);
}
return null;
}
}
// 사용 예시
const App: React.FC = () => {
const handler1 = new ConcreteHandler1();
const handler2 = new ConcreteHandler2();
handler1.setNext(handler2);
const result = handler1.handle('Request2');
return (
<div>
<h1>Chain of Responsibility Pattern</h1>
<p>{result}</p>
</div>
);
};
export default App;
2. Command (커맨드)
정의: 요청을 객체로 캡슐화하여 다양한 요청, 큐잉, 로깅 및 취소 작업을 지원합니다.
특징:
- 요청을 객체로 캡슐화하여 유연하게 관리할 수 있습니다.
- 요청을 큐에 저장하거나, 로깅하고, 실행 취소할 수 있습니다.
사용 시기:
- 작업을 캡슐화하여 실행 취소나 재실행을 지원해야 할 때.
- 요청을 큐에 저장하거나 로깅해야 할 때.
예시:
import React from 'react';
// Command 인터페이스 정의
interface Command {
execute: () => void;
}
// Concrete Command 구현
class LightOnCommand implements Command {
private light: Light;
constructor(light: Light) {
this.light = light;
}
execute(): void {
this.light.on();
}
}
class LightOffCommand implements Command {
private light: Light;
constructor(light: Light) {
this.light = light;
}
execute(): void {
this.light.off();
}
}
// Receiver 클래스 정의
class Light {
on() {
console.log('Light is ON');
}
off() {
console.log('Light is OFF');
}
}
// Invoker 클래스 정의
class RemoteControl {
private command: Command | null = null;
setCommand(command: Command) {
this.command = command;
}
pressButton() {
if (this.command) {
this.command.execute();
}
}
}
// 사용 예시
const App: React.FC = () => {
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();
return (
<div>
<h1>Command Pattern</h1>
<p>Check the console for output.</p>
</div>
);
};
export default App;
3. Interpreter (인터프리터)
정의: 언어의 문법을 정의하고 해석합니다.
특징:
- 언어의 문법을 정의하고, 해석기를 통해 문장을 해석합니다.
- 특정 도메인 언어의 구현에 유용합니다.
사용 시기:
- 특정 도메인 언어를 정의하고 해석해야 할 때.
- 복잡한 문법을 단순화하고자 할 때.
예시:
import React from 'react';
// Abstract Expression 인터페이스 정의
interface Expression {
interpret: (context: string) => boolean;
}
// Terminal Expression 구현
class TerminalExpression implements Expression {
private data: string;
constructor(data: string) {
this.data = data;
}
interpret(context: string): boolean {
return context.includes(this.data);
}
}
// Or Expression 구현
class OrExpression implements Expression {
private expr1: Expression;
private expr2: Expression;
constructor(expr1: Expression, expr2: Expression) {
this.expr1 = expr1;
this.expr2 = expr2;
}
interpret(context: string): boolean {
return this.expr1.interpret(context) || this.expr2.interpret(context);
}
}
// 사용 예시
const App: React.FC = () => {
const expr1 = new TerminalExpression('Hello');
const expr2 = new TerminalExpression('World');
const orExpr = new OrExpression(expr1, expr2);
const result = orExpr.interpret('Hello');
return (
<div>
<h1>Interpreter Pattern</h1>
<p>{`Interpret result: ${result}`}</p>
</div>
);
};
export default App;
4. Iterator (이터레이터)
정의: 집합체의 요소들을 순차적으로 접근할 수 있는 방법을 제공합니다.
특징:
- 집합체의 내부 구조를 노출하지 않고 요소들에 접근할 수 있습니다.
- 요소들에 순차적으로 접근할 수 있는 방법을 제공합니다.
사용 시기:
- 집합체의 요소들에 순차적으로 접근해야 할 때.
- 집합체의 내부 구조를 노출하지 않고 요소들에 접근하고자 할 때.
예시:
Copy code
import React from 'react';
// Iterator 인터페이스 정의
interface Iterator<T> {
next: () => T | null;
hasNext: () => boolean;
}
// Concrete Iterator 구현
class ArrayIterator<T> implements Iterator<T> {
private collection: T[];
private position: number = 0;
constructor(collection: T[]) {
this.collection = collection;
}
next(): T | null {
if (this.hasNext()) {
return this.collection[this.position++];
}
return null;
}
hasNext(): boolean {
return this.position < this.collection.length;
}
}
// 사용 예시
const App: React.FC = () => {
const items = ['Item1', 'Item2', 'Item3'];
const iterator = new ArrayIterator<string>(items);
const results: string[] = [];
while (iterator.hasNext()) {
const item = iterator.next();
if (item) {
results.push(item);
}
}
return (
<div>
<h1>Iterator Pattern</h1>
<ul>
{results.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
};
export default App;
5. Mediator (미디에이터)
정의: 객체들 간의 상호작용을 캡슐화합니다.
특징:
- 객체들 간의 직접적인 상호작용을 피하고, 중재자를 통해 상호작용합니다.
- 객체들 간의 결합도를 낮출 수 있습니다.
사용 시기:
- 객체들 간의 복잡한 상호작용을 단순화하고자 할 때.
- 객체들 간의 결합도를 낮추고자 할 때.
예시:
import React from 'react';
// Mediator 인터페이스 정의
interface Mediator {
notify: (sender: object, event: string) => void;
}
// Concrete Mediator 구현
class ConcreteMediator implements Mediator {
private componentA: ComponentA;
private componentB: ComponentB;
constructor(componentA: ComponentA, componentB: ComponentB) {
this.componentA = componentA;
this.componentA.setMediator(this);
this.componentB = componentB;
this.componentB.setMediator(this);
}
notify(sender: object, event: string): void {
if (event === 'A') {
console.log('Mediator reacts on A and triggers following operations:');
this.componentB.doC();
}
if (event === 'B') {
console.log('Mediator reacts on B and triggers following operations:');
this.componentA.doB();
}
}
}
// Base Component 클래스 정의
class BaseComponent {
protected mediator: Mediator | null = null;
setMediator(mediator: Mediator): void {
this.mediator = mediator;
}
}
// Concrete Components 구현
class ComponentA extends BaseComponent {
doA(): void {
console.log('Component A does A.');
if (this.mediator) {
this.mediator.notify(this, 'A');
}
}
doB(): void {
console.log('Component A does B.');
}
}
class ComponentB extends BaseComponent {
doC(): void {
console.log('Component B does C.');
}
doD(): void {
console.log('Component B does D.');
if (this.mediator) {
this.mediator.notify(this, 'B');
}
}
}
// 사용 예시
const App: React.FC = () => {
const componentA = new ComponentA();
const componentB = new ComponentB();
new ConcreteMediator(componentA, componentB);
componentA.doA();
componentB.doD();
return (
<div>
<h1>Mediator Pattern</h1>
<p>Check the console for output.</p>
</div>
);
};
export default App;
6. Memento (메멘토)
정의: 객체의 상태를 저장하고 복원합니다.
특징:
- 객체의 상태를 저장하고 나중에 복원할 수 있습니다.
- 객체의 내부 상태를 캡슐화하여 외부에 노출하지 않습니다.
사용 시기:
- 객체의 상태를 저장하고 나중에 복원해야 할 때.
- 객체의 상태 변경을 추적하고, 특정 시점으로 되돌려야 할 때.
예시:
import React from 'react';
// Memento 클래스 정의
class Memento {
private state: string;
constructor(state: string) {
this.state = state;
}
getState(): string {
return this.state;
}
}
// Originator 클래스 정의
class Originator {
private state: string;
setState(state: string): void {
this.state = state;
console.log(`Originator: Setting state to ${state}`);
}
save(): Memento {
console.log('Originator: Saving to Memento.');
return new Memento(this.state);
}
restore(memento: Memento): void {
this.state = memento.getState();
console.log(`Originator: State after restoring from Memento: ${this.state}`);
}
}
// Caretaker 클래스 정의
class Caretaker {
private mementos: Memento[] = [];
private originator: Originator;
constructor(originator: Originator) {
this.originator = originator;
}
save(): void {
console.log('Caretaker: Saving Originator\'s state...');
this.mementos.push(this.originator.save());
}
undo(): void {
if (this.mementos.length) {
const memento = this.mementos.pop();
if (memento) {
console.log('Caretaker: Restoring state to previous state...');
this.originator.restore(memento);
}
}
}
}
// 사용 예시
const App: React.FC = () => {
const originator = new Originator();
const caretaker = new Caretaker(originator);
originator.setState('State1');
caretaker.save();
originator.setState('State2');
caretaker.save();
originator.setState('State3');
caretaker.undo();
originator.setState('State4');
caretaker.undo();
return (
<div>
<h1>Memento Pattern</h1>
<p>Check the console for output.</p>
</div>
);
};
export default App;
7. Observer (옵저버)
정의: 객체의 상태 변화에 따라 다른 객체들이 통보를 받고 자동으로 업데이트됩니다.
특징:
- 객체 간의 의존성을 줄이고, 상태 변화를 효과적으로 관리할 수 있습니다.
- 객체의 상태 변화에 따른 자동 업데이트가 가능합니다.
사용 시기:
- 객체의 상태 변화를 관찰하고, 자동으로 다른 객체들에게 통보해야 할 때.
- 객체 간의 의존성을 줄이고, 상태 변화를 효과적으로 관리하고자 할 때.
예시:
import React, { useState, useEffect } from 'react';
// Subject 클래스 정의
class Subject {
private observers: Observer[] = [];
addObserver(observer: Observer): void {
this.observers.push(observer);
}
removeObserver(observer: Observer): void {
this.observers = this.observers.filter(obs => obs !== observer);
}
notifyObservers(state: string): void {
this.observers.forEach(observer => observer.update(state));
}
}
// Observer 인터페이스 정의
interface Observer {
update: (state: string) => void;
}
// Concrete Observer 구현
class ConcreteObserver implements Observer {
private id: number;
constructor(id: number) {
this.id = id;
}
update(state: string): void {
console.log(`Observer ${this.id} received state: ${state}`);
}
}
// 사용 예시
const App: React.FC = () => {
const subject = new Subject();
const observer1 = new ConcreteObserver(1);
const observer2 = new ConcreteObserver(2);
subject.addObserver(observer1);
subject.addObserver(observer2);
subject.notifyObservers('State1');
subject.notifyObservers('State2');
return (
<div>
<h1>Observer Pattern</h1>
<p>Check the console for output.</p>
</div>
);
};
export default App;
8. State (상태)
정의: 객체의 내부 상태에 따라 행동이 변경됩니다.
특징:
- 객체의 상태를 캡슐화하고, 상태에 따른 행동을 관리할 수 있습니다.
- 상태 전환을 통해 객체의 행동을 동적으로 변경할 수 있습니다.
사용 시기:
- 객체의 상태에 따라 행동이 달라져야 할 때.
- 상태 전환을 통해 객체의 행동을 동적으로 변경하고자 할 때.
예시:
import React from 'react';
// State 인터페이스 정의
interface State {
handle: (context: Context) => void;
}
// Concrete States 구현
class StateA implements State {
handle(context: Context): void {
console.log('State A handling request.');
context.setState(new StateB());
}
}
class StateB implements State {
handle(context: Context): void {
console.log('State B handling request.');
context.setState(new StateA());
}
}
// Context 클래스 정의
class Context {
private state: State;
constructor(state: State) {
this.state = state;
}
setState(state: State): void {
this.state = state;
}
request(): void {
this.state.handle(this);
}
}
// 사용 예시
const App: React.FC = () => {
const context = new Context(new StateA());
context.request();
context.request();
return (
<div>
<h1>State Pattern</h1>
<p>Check the console for output.</p>
</div>
);
};
export default App;
9. Strategy (전략)
정의: 알고리즘 군을 정의하고, 각 알고리즘을 캡슐화하여 상호 교체 가능하게 합니다.
특징:
- 알고리즘을 캡슐화하여 유연하게 교체할 수 있습니다.
- 클라이언트는 알고리즘의 구체적인 구현을 모른 채로 사용할 수 있습니다.
사용 시기:
- 알고리즘을 유연하게 교체하고자 할 때.
- 클라이언트가 알고리즘의 구체적인 구현을 몰라도 될 때.
예시:
import React from 'react';
// Strategy 인터페이스 정의
interface Strategy {
execute: (a: number, b: number) => number;
}
// Concrete Strategies 구현
class AddStrategy implements Strategy {
execute(a: number, b: number): number {
return a + b;
}
}
class SubtractStrategy implements Strategy {
execute(a: number, b: number): number {
return a - b;
}
}
// Context 클래스 정의
class Context {
private strategy: Strategy;
setStrategy(strategy: Strategy): void {
this.strategy = strategy;
}
executeStrategy(a: number, b: number): number {
return this.strategy.execute(a, b);
}
}
// 사용 예시
const App: React.FC = () => {
const context = new Context();
context.setStrategy(new AddStrategy());
console.log(context.executeStrategy(10, 5)); // 15
context.setStrategy(new SubtractStrategy());
console.log(context.executeStrategy(10, 5)); // 5
return (
<div>
<h1>Strategy Pattern</h1>
<p>Check the console for output.</p>
</div>
);
};
export default App;
10. Template Method (템플릿 메서드)
정의: 알고리즘의 구조를 정의하고, 하위 클래스가 알고리즘의 특정 단계를 구현할 수 있게 합니다.
특징:
- 알고리즘의 구조를 정의하고, 하위 클래스가 특정 단계를 구현할 수 있습니다.
- 코드의 재사용성과 유연성을 높일 수 있습니다.
사용 시기:
- 알고리즘의 구조를 정의하고, 하위 클래스가 특정 단계를 구현해야 할 때.
- 알고리즘의 일부 단계를 재사용하면서, 다른 단계를 유연하게 변경하고자 할 때.
예시:
import React from 'react';
// Abstract Class 정의
abstract class AbstractClass {
templateMethod(): void {
this.baseOperation1();
this.requiredOperations1();
this.baseOperation2();
this.requiredOperations2();
}
baseOperation1(): void {
console.log('AbstractClass says: I am doing the bulk of the work');
}
baseOperation2(): void {
console.log('AbstractClass says: But I let subclasses override some operations');
}
protected abstract requiredOperations1(): void;
protected abstract requiredOperations2(): void;
}
// Concrete Classes 구현
class ConcreteClass1 extends AbstractClass {
protected requiredOperations1(): void {
console.log('ConcreteClass1 says: Implemented Operation1');
}
protected requiredOperations2(): void {
console.log('ConcreteClass1 says: Implemented Operation2');
}
}
class ConcreteClass2 extends AbstractClass {
protected requiredOperations1(): void {
console.log('ConcreteClass2 says: Implemented Operation1');
}
protected requiredOperations2(): void {
console.log('ConcreteClass2 says: Implemented Operation2');
}
}
// 사용 예시
const App: React.FC = () => {
const concreteClass1 = new ConcreteClass1();
const concreteClass2 = new ConcreteClass2();
concreteClass1.templateMethod();
concreteClass2.templateMethod();
return (
<div>
<h1>Template Method Pattern</h1>
<p>Check the console for output.</p>
</div>
);
};
export default App;
11. Visitor (비지터)
정의: 객체 구조를 순회하면서 수행할 작업을 정의합니다.
특징:
- 객체 구조를 순회하면서 다양한 작업을 수행할 수 있습니다.
- 작업을 객체 구조와 분리하여 독립적으로 정의할 수 있습니다.
사용 시기:
- 객체 구조를 순회하면서 다양한 작업을 수행해야 할 때.
- 작업을 객체 구조와 분리하여 독립적으로 정의하고자 할 때.
예시:
import React from 'react';
// Visitor 인터페이스 정의
interface Visitor {
visitConcreteElementA: (element: ConcreteElementA) => void;
visitConcreteElementB: (element: ConcreteElementB) => void;
}
// Element 인터페이스 정의
interface Element {
accept: (visitor: Visitor) => void;
}
// Concrete Elements 구현
class ConcreteElementA implements Element {
accept(visitor: Visitor): void {
visitor.visitConcreteElementA(this);
}
operationA(): string {
return 'ConcreteElementA';
}
}
class ConcreteElementB implements Element {
accept(visitor: Visitor): void {
visitor.visitConcreteElementB(this);
}
operationB(): string {
return 'ConcreteElementB';
}
}
// Concrete Visitor 구현
class ConcreteVisitor implements Visitor {
visitConcreteElementA(element: ConcreteElementA): void {
console.log(`${element.operationA()} visited by ConcreteVisitor`);
}
visitConcreteElementB(element: ConcreteElementB): void {
console.log(`${element.operationB()} visited by ConcreteVisitor`);
}
}
// 사용 예시
const App: React.FC = () => {
const elements: Element[] = [new ConcreteElementA(), new ConcreteElementB()];
const visitor = new ConcreteVisitor();
elements.forEach(element => element.accept(visitor));
return (
<div>
<h1>Visitor Pattern</h1>
<p>Check the console for output.</p>
</div>
);
};
export default App;
이로써 OOP의 GoF 모든 디자인 패턴을 다뤄보았다.
정리하면서 코드를 이해해보려고 노력을 했을뿐, 아직 프로젝트에 제대로 적용해본 패턴은 그리 많지 않은 것 같다.
계속 써버릇하면서 적재적소에 쓸 수 있도록 노력해야겠다.