쪽지발송 성공
Click here
재능넷 이용방법
재능넷 이용방법 동영상편
가입인사 이벤트
판매 수수료 안내
안전거래 TIP
재능인 인증서 발급안내

🌲 지식인의 숲 🌲

🌳 디자인
🌳 음악/영상
🌳 문서작성
🌳 번역/외국어
🌳 프로그램개발
🌳 마케팅/비즈니스
🌳 생활서비스
🌳 철학
🌳 과학
🌳 수학
🌳 역사
해당 지식과 관련있는 인기재능

소개안드로이드 기반 어플리케이션 개발 후 서비스를 하고 있으며 스타트업 경험을 통한 앱 및 서버, 관리자 페이지 개발 경험을 가지고 있습니다....

안녕하세요.신호처리를 전공한 개발자 입니다. 1. 영상신호처리, 생체신호처리 알고리즘 개발2. 안드로이드 앱 개발 3. 윈도우 프로그램...

 안녕하세요. 안드로이드 기반 개인 앱, 프로젝트용 앱부터 그 이상 기능이 추가된 앱까지 제작해 드립니다.  - 앱 개발 툴: 안드로이드...

자바스크립트 디자인 패턴

2024-09-22 17:01:22

재능넷
조회수 599 댓글수 0

자바스크립트 디자인 패턴: 효율적인 코드 구조화의 비밀 🏗️

 

 

안녕하세요, 프로그래밍 애호가 여러분! 오늘은 자바스크립트 개발자라면 반드시 알아야 할 주제, 바로 '자바스크립트 디자인 패턴'에 대해 깊이 있게 탐구해보려고 합니다. 이 글을 통해 여러분은 코드의 구조를 개선하고, 유지보수성을 높이며, 더 효율적인 프로그래밍을 할 수 있는 방법을 배우게 될 거예요.

자바스크립트는 웹 개발의 핵심 언어로, 프론트엔드부터 백엔드까지 다양한 영역에서 활용되고 있습니다. 그만큼 복잡한 애플리케이션을 다루는 경우가 많아졌고, 이에 따라 코드의 구조화와 패턴의 중요성이 더욱 부각되고 있죠. 특히 재능넷과 같은 대규모 플랫폼을 개발할 때는 이러한 디자인 패턴의 적용이 프로젝트의 성공을 좌우할 수 있습니다.

이 글에서는 자바스크립트 디자인 패턴의 기본 개념부터 실제 적용 사례까지, 단계별로 상세히 알아볼 예정입니다. 각 패턴의 장단점을 비교하고, 어떤 상황에서 어떤 패턴을 선택해야 할지에 대한 인사이트도 제공할 거예요. 또한, 최신 ES6+ 문법을 활용한 모던한 패턴 구현 방법도 소개하겠습니다.

자, 그럼 지금부터 자바스크립트 디자인 패턴의 세계로 함께 떠나볼까요? 🚀

1. 디자인 패턴의 기초 이해하기 📚

디자인 패턴이란 무엇일까요? 간단히 말해, 소프트웨어 설계 과정에서 자주 발생하는 문제들에 대한 일반적인 해결책을 말합니다. 이는 마치 건축에서 사용되는 청사진과 같은 역할을 하죠. 프로그래머들이 자주 마주치는 문제들에 대해 검증된 해결 방법을 제공함으로써, 더 효율적이고 유지보수가 쉬운 코드를 작성할 수 있게 도와줍니다.

자바스크립트에서 디자인 패턴을 사용하면 다음과 같은 이점을 얻을 수 있습니다:

  • 코드의 재사용성 증가 ♻️
  • 유지보수의 용이성 🛠️
  • 확장성 있는 구조 설계 🌱
  • 개발자 간 의사소통 개선 🗣️
  • 버그 발생 가능성 감소 🐛

디자인 패턴은 크게 세 가지 카테고리로 나눌 수 있습니다:

  1. 생성 패턴(Creational Patterns): 객체 생성 메커니즘을 다루는 패턴
  2. 구조 패턴(Structural Patterns): 객체들의 구조를 설계하는 패턴
  3. 행동 패턴(Behavioral Patterns): 객체들 간의 상호작용을 다루는 패턴

이제 각 카테고리별로 주요 패턴들을 살펴보겠습니다. 각 패턴에 대해 개념, 사용 시기, 장단점, 그리고 실제 코드 예제를 통해 자세히 알아볼 거예요.

디자인 패턴 카테고리 생성 패턴 구조 패턴 행동 패턴

이 그림은 디자인 패턴의 세 가지 주요 카테고리를 시각적으로 표현한 것입니다. 각 원은 하나의 카테고리를 나타내며, 이들이 서로 연결되어 있음을 보여줍니다. 이는 실제로 많은 패턴들이 여러 카테고리의 특성을 동시에 가지고 있음을 의미하죠.

자, 이제 각 카테고리별로 주요 패턴들을 자세히 살펴보겠습니다. 준비되셨나요? 😊

2. 생성 패턴 (Creational Patterns) 🏭

생성 패턴은 객체 생성 메커니즘을 다루는 패턴입니다. 이 패턴들은 객체를 생성하는 과정을 유연하게 만들어, 상황에 따라 적절한 객체를 생성할 수 있도록 도와줍니다. 주요 생성 패턴으로는 싱글톤(Singleton), 팩토리 메서드(Factory Method), 추상 팩토리(Abstract Factory), 빌더(Builder), 프로토타입(Prototype) 등이 있습니다.

2.1 싱글톤 패턴 (Singleton Pattern) 🔒

싱글톤 패턴은 클래스의 인스턴스가 오직 하나만 생성되도록 보장하는 패턴입니다. 이 패턴은 전역 상태를 관리하거나, 리소스를 공유할 때 유용하게 사용됩니다.

장점:

  • 글로벌 접근 지점 제공
  • 한 번의 인스턴스화로 메모리 절약

단점:

  • 단일 책임 원칙(SRP) 위반 가능성
  • 테스트의 어려움

다음은 ES6+를 사용한 싱글톤 패턴의 구현 예시입니다:


class Singleton {
  constructor() {
    if (Singleton.instance) {
      return Singleton.instance;
    }
    Singleton.instance = this;
    this.data = [];
  }

  addItem(item) {
    this.data.push(item);
  }

  getItems() {
    return this.data;
  }
}

const instance1 = new Singleton();
const instance2 = new Singleton();

console.log(instance1 === instance2); // true

instance1.addItem('Apple');
console.log(instance2.getItems()); // ['Apple']

이 예제에서 Singleton 클래스는 항상 같은 인스턴스를 반환합니다. 따라서 instance1instance2는 동일한 객체를 참조하게 됩니다.

2.2 팩토리 메서드 패턴 (Factory Method Pattern) 🏭

팩토리 메서드 패턴은 객체 생성 로직을 서브클래스로 분리하여, 클라이언트 코드와 객체 생성 코드를 분리합니다. 이 패턴은 객체 생성의 유연성을 높이고, 코드의 결합도를 낮추는 데 도움을 줍니다.

장점:

  • 객체 생성 로직의 중앙화
  • 코드 재사용성 증가
  • 유연한 확장 가능

단점:

  • 클래스 수 증가로 인한 복잡성 증가

팩토리 메서드 패턴의 예시를 살펴보겠습니다:


class Vehicle {
  constructor(type) {
    this.type = type;
  }

  getType() {
    return this.type;
  }
}

class Car extends Vehicle {
  constructor() {
    super('Car');
  }
}

class Truck extends Vehicle {
  constructor() {
    super('Truck');
  }
}

class VehicleFactory {
  createVehicle(type) {
    switch(type) {
      case 'car':
        return new Car();
      case 'truck':
        return new Truck();
      default:
        throw new Error('Unknown vehicle type.');
    }
  }
}

const factory = new VehicleFactory();
const car = factory.createVehicle('car');
const truck = factory.createVehicle('truck');

console.log(car.getType());  // 'Car'
console.log(truck.getType());  // 'Truck'

이 예제에서 VehicleFactory 클래스는 차량 객체를 생성하는 팩토리 역할을 합니다. 클라이언트 코드는 구체적인 차량 클래스를 알 필요 없이, 팩토리를 통해 원하는 유형의 차량 객체를 생성할 수 있습니다.

2.3 추상 팩토리 패턴 (Abstract Factory Pattern) 🏭🏭

추상 팩토리 패턴은 팩토리 메서드 패턴을 한 단계 더 추상화한 패턴입니다. 이 패턴은 관련된 객체들의 집합을 생성하기 위한 인터페이스를 제공합니다.

장점:

  • 관련된 객체 집합의 일관성 보장
  • 구체적인 클래스와의 결합도 감소

단점:

  • 새로운 종류의 제품을 추가할 때 복잡성 증가

추상 팩토리 패턴의 예시를 살펴보겠습니다:


// 추상 제품 클래스들
class Button {
  click() {
    console.log('Button clicked');
  }
}

class Checkbox {
  check() {
    console.log('Checkbox checked');
  }
}

// 구체적인 제품 클래스들
class WindowsButton extends Button {
  click() {
    console.log('Windows button clicked');
  }
}

class WindowsCheckbox extends Checkbox {
  check() {
    console.log('Windows checkbox checked');
  }
}

class MacButton extends Button {
  click() {
    console.log('Mac button clicked');
  }
}

class MacCheckbox extends Checkbox {
  check() {
    console.log('Mac checkbox checked');
  }
}

// 추상 팩토리 인터페이스
class GUIFactory {
  createButton() {}
  createCheckbox() {}
}

// 구체적인 팩토리 클래스들
class WindowsFactory extends GUIFactory {
  createButton() {
    return new WindowsButton();
  }
  createCheckbox() {
    return new WindowsCheckbox();
  }
}

class MacFactory extends GUIFactory {
  createButton() {
    return new MacButton();
  }
  createCheckbox() {
    return new MacCheckbox();
  }
}

// 클라이언트 코드
function createUI(factory) {
  const button = factory.createButton();
  const checkbox = factory.createCheckbox();
  button.click();
  checkbox.check();
}

// 사용 예
const windowsFactory = new WindowsFactory();
const macFactory = new MacFactory();

console.log('Windows UI:');
createUI(windowsFactory);

console.log('Mac UI:');
createUI(macFactory);

이 예제에서는 운영 체제에 따라 다른 UI 컴포넌트를 생성하는 추상 팩토리 패턴을 구현했습니다. WindowsFactoryMacFactory는 각각 Windows와 Mac 스타일의 버튼과 체크박스를 생성합니다.

2.4 빌더 패턴 (Builder Pattern) 🏗️

빌더 패턴은 복잡한 객체의 생성 과정과 표현 방법을 분리하여, 동일한 생성 절차에서 서로 다른 표현 결과를 만들 수 있게 하는 패턴입니다.

장점:

  • 복잡한 객체 생성 과정의 단순화
  • 객체 생성 코드의 재사용성 증가
  • 객체의 내부 표현 변경에 대한 유연성

단점:

  • 각 구체적 제품마다 별도의 ConcreteBuilder 클래스 필요

빌더 패턴의 예시를 살펴보겠습니다:


class Burger {
  constructor() {
    this.bun = '';
    this.patty = '';
    this.cheese = '';
    this.toppings = [];
  }

  toString() {
    return `Burger with ${this.bun} bun, ${this.patty} patty, ${this.cheese} cheese, and toppings: ${this.toppings.join(', ')}`;
  }
}

class BurgerBuilder {
  constructor() {
    this.burger = new Burger();
  }

  addBun(bun) {
    this.burger.bun = bun;
    return this;
  }

  addPatty(patty) {
    this.burger.patty = patty;
    return this;
  }

  addCheese(cheese) {
    this.burger.cheese = cheese;
    return this;
  }

  addTopping(topping) {
    this.burger.toppings.push(topping);
    return this;
  }

  build() {
    return this.burger;
  }
}

// 사용 예
const burger = new BurgerBuilder()
  .addBun('sesame')
  .addPatty('beef')
  .addCheese('cheddar')
  .addTopping('lettuce')
  .addTopping('tomato')
  .build();

console.log(burger.toString());
// 출력: Burger with sesame bun, beef patty, cheddar cheese, and toppings: lettuce, tomato

이 예제에서 BurgerBuilder 클래스는 Burger 객체를 단계별로 생성할 수 있게 해줍니다. 각 메서드는 빌더 객체 자신을 반환하므로, 메서드 체이닝을 통해 버거를 구성할 수 있습니다.

2.5 프로토타입 패턴 (Prototype Pattern) 🐑

프로토타입 패턴은 기존 객체를 복제하여 새로운 객체를 생성하는 패턴입니다. 이 패턴은 객체 생성 비용이 높을 때 유용하게 사용됩니다.

장점:

  • 복잡한 객체를 효율적으로 생성 가능
  • 서브클래싱의 필요성 감소

단점:

  • 순환 참조가 있는 복잡한 객체의 복제가 어려울 수 있음

프로토타입 패턴의 예시를 살펴보겠습니다:


class Car {
  constructor(make, model, color) {
    this.make = make;
    this.model = model;
    this.color = color;
  }

  clone() {
    return new Car(this.make, this.model, this.color);
  }

  toString() {
    return `${this.make} ${this.model} in ${this.color}`;
  }
}

// 사용 예
const originalCar = new Car('Toyota', 'Camry', 'blue');
const clonedCar = originalCar.clone();

console.log(originalCar.toString()); // Toyota Camry in blue
console.log(clonedCar.toString());   // Toyota Camry in blue

clonedCar.color = 'red';
console.log(clonedCar.toString());   // Toyota Camry in red

이 예제에서 Car 클래스는 clone 메서드를 통해 자신의 복제본을 생성할 수 있습니다. 이를 통해 기존 객체의 속성을 그대로 가진 새로운 객체를 쉽게 만들 수 있습니다.

이렇게 생성 패턴들은 객체 생성 과정을 추상화하고 유연성을 제공함으로써, 코드의 재사용성과 확장성을 높여줍니다. 각 패턴은 특정 상황에 적합하므로, 상황에 맞는 패턴을 선택하여 사용하는 것이 중요합니다.

다음 섹션에서는 구조 패턴에 대해 알아보겠습니다. 구조 패턴은 객체들이나 클래스들을 더 큰 구조로 조합하는 방법을 다루는 패턴입니다. 준비되셨나요? 계속해서 자바스크립트 디자인 패턴의 세계를 탐험해봅시다! 🚀

3. 구조 패턴 (Structural Patterns) 🏗️

구조 패턴은 클래스나 객체를 조합하여 더 큰 구조를 만드는 패턴입니다. 이 패턴들은 서로 다른 인터페이스를 가진 객체들을 함께 동작하게 만들거나, 기능을 조합하여 새로운 기능을 만들어내는 데 사용됩니다. 주요 구조 패턴으로는 어댑터(Adapter), 브리지(Bridge), 컴포지트(Composite), 데코레이터(Decorator), 퍼사드(Facade), 플라이웨이트(Flyweight), 프록시(Proxy) 등이 있습니다.

3.1 어댑터 패턴 (Adapter Pattern) 🔌

어댑터 패턴은 호환되지 않는 인터페이스를 가진 객체들이 협력할 수 있도록 해주는 패턴입니다. 이는 마치 전기 플러그 어댑터처럼, 서로 다른 인터페이스를 연결해주는 역할을 합니다.

장점:

  • 기존 코드를 변경하지 않고 새로운 인터페이스 사용 가능
  • 코드의 재사용성 증가

단점:

  • 새로운 클래스 도입으로 인한 복잡성 증가

어댑터 패턴의 예시를 살펴보겠습니다:


// 기존 인터페이스
class OldPrinter {
  print(text) {
    console.log(`Old Printer: ${text}`);
  }
}

// 새로운 인터페이스
class NewPrinter {
  printModern(text) {
    console.log(`New Printer: ${text}`);
  }
}

// 어댑터
class PrinterAdapter {
  constructor(newPrinter) {
    this.newPrinter = newPrinter;
  }

  print(text) {
    this.newPrinter.printModern(text);
  }
}

// 클라이언트 코드
function clientCode(printer) {
  printer.print("Hello, World!");
}

// 사용 예
const oldPrinter = new OldPrinter();
clientCode(oldPrinter);  // 출력: Old Printer: Hello, World!

const newPrinter = new NewPrinter();
const adapter = new PrinterAdapter(newPrinter);
clientCode(adapter);  // 출력: New Printer: Hello, World!

이 예제에서 PrinterAdapterNewPrinterprintModern 메서드를 OldPrinterprint 인터페이스에 맞게 변환합니다. 이를 통해 클라이언트 코드는 변경 없이 새로운 프린터를 사용할 수 있습니다.

3.2 브리지 패턴 (Bridge Pattern) 🌉

브리지 패턴은 추상화와 구현을 분리하여 둘을 독립적으로 변형할 수 있게 해주는 패턴입니다. 이 패턴은 큰 클래스 또는 밀접하게 관련된 클래스들의 집합을 두 개의 개별 계층구조(추상화와 구현)로 나눕니다.

장점:

  • 추상화와 구현의 분리로 유연성 증가
  • 단일 책임 원칙 준수

단점:

  • 복잡성 증가

브리지 패턴의 예시를 살펴보겠습니다:


// 구현부 (Implementation)
class DrawAPI {
  drawCircle(x, y, radius) {}
}

class RedCircle extends DrawAPI {
  drawCircle(x, y, radius) {
    console.log(`Drawing Red circle at (${x}, ${y}) with radius ${radius}`);
  }
}

class BlueCircle extends DrawAPI {
  drawCircle(x, y, radius) {
    console.log(`Drawing Blue circle at (${x}, ${y}) with radius ${radius}`);
  }
}

// 추상화 (Abstraction)
class Shape {
  constructor(drawAPI) {
    this.drawAPI = drawAPI;
  }

  draw() {}
}

class Circle extends Shape {
  constructor(x, y, radius, drawAPI) {
    super(drawAPI);
    this.x = x;
    this.y = y;
    this.radius = radius;
  }

  draw() {
    this.drawAPI.drawCircle(this.x, this.y, this.radius);
  }
}

// 사용 예
const redCircle = new Circle(100, 100, 10, new RedCircle());
const blueCircle = new Circle(200, 200, 20, new BlueCircle());

redCircle.draw();  // 출력: Drawing Red circle at (100, 100) with radius 10
blueCircle.draw(); // 출력: Drawing Blue circle at (200, 200) with radius 20

이 예제에서 ShapeCircle은 추상화 부분을, DrawAPI와 그 구현체들은 구현 부분을 나타냅니다. 브리지 패턴을 통해 원의 형태(추상화)와 그리는 방법(구현)을 독립적으로 변경할 수 있습니다.

3.3 컴포지트 패턴 (Composite Pattern) 🌳

컴포지트 패턴은 객체들을 트리 구조로 구성하여 부분-전체 계층을 표현하는 패턴입니다. 이 패턴을 사용하면 클라이언트가 개별 객체와 복합 객체를 동일하게 다룰 수 있습니다.

장점:

  • 복잡한 트리 구조를 효율적으로 다룰 수 있음
  • 클라이언트 코드 단순화

단점:

  • 공통 인터페이스 설계의 어려움

컴포지트 패턴의 예 시를 살펴보겠습니다:


// Component
class FileSystemComponent {
  constructor(name) {
    this.name = name;
  }

  display(indent = 0) {
    console.log(' '.repeat(indent) + this.name);
  }

  getSize() {}
}

// Leaf
class File extends FileSystemComponent {
  constructor(name, size) {
    super(name);
    this.size = size;
  }

  getSize() {
    return this.size;
  }
}

// Composite
class Directory extends FileSystemComponent {
  constructor(name) {
    super(name);
    this.children = [];
  }

  add(component) {
    this.children.push(component);
  }

  remove(component) {
    const index = this.children.indexOf(component);
    if (index !== -1) {
      this.children.splice(index, 1);
    }
  }

  display(indent = 0) {
    super.display(indent);
    this.children.forEach(child => child.display(indent + 2));
  }

  getSize() {
    return this.children.reduce((sum, child) => sum + child.getSize(), 0);
  }
}

// 사용 예
const root = new Directory('root');
const home = new Directory('home');
const user1 = new Directory('user1');
const user2 = new Directory('user2');
const file1 = new File('file1.txt', 100);
const file2 = new File('file2.txt', 200);
const file3 = new File('file3.txt', 300);

root.add(home);
home.add(user1);
home.add(user2);
user1.add(file1);
user1.add(file2);
user2.add(file3);

root.display();
console.log('Total size:', root.getSize());

이 예제에서 FileSystemComponent는 공통 인터페이스를 정의하고, File은 리프 노드를, Directory는 복합 노드를 나타냅니다. 클라이언트는 개별 파일과 디렉토리를 동일한 방식으로 다룰 수 있습니다.

3.4 데코레이터 패턴 (Decorator Pattern) 🎀

데코레이터 패턴은 객체에 동적으로 새로운 책임을 추가할 수 있게 해주는 패턴입니다. 이 패턴은 서브클래스를 만드는 것보다 유연한 방법으로 기능을 확장할 수 있게 해줍니다.

장점:

  • 기존 코드를 수정하지 않고 기능 확장 가능
  • 런타임에 동적으로 기능 추가/제거 가능

단점:

  • 데코레이터를 너무 많이 사용하면 코드가 복잡해질 수 있음

데코레이터 패턴의 예시를 살펴보겠습니다:


// Component
class Coffee {
  cost() {
    return 5;
  }

  description() {
    return 'Simple coffee';
  }
}

// Decorator
class CoffeeDecorator {
  constructor(coffee) {
    this.coffee = coffee;
  }

  cost() {
    return this.coffee.cost();
  }

  description() {
    return this.coffee.description();
  }
}

// Concrete Decorators
class Milk extends CoffeeDecorator {
  cost() {
    return this.coffee.cost() + 2;
  }

  description() {
    return this.coffee.description() + ', milk';
  }
}

class Sugar extends CoffeeDecorator {
  cost() {
    return this.coffee.cost() + 1;
  }

  description() {
    return this.coffee.description() + ', sugar';
  }
}

// 사용 예
let coffee = new Coffee();
console.log(coffee.description(), '-', coffee.cost());

coffee = new Milk(coffee);
console.log(coffee.description(), '-', coffee.cost());

coffee = new Sugar(coffee);
console.log(coffee.description(), '-', coffee.cost());

이 예제에서 MilkSugar는 데코레이터로, 기본 Coffee 객체에 새로운 기능(우유와 설탕 추가)을 동적으로 추가합니다.

3.5 퍼사드 패턴 (Facade Pattern) 🏢

퍼사드 패턴은 복잡한 서브시스템에 대한 간단한 인터페이스를 제공하는 패턴입니다. 이 패턴은 클라이언트와 서브시스템 사이에 퍼사드라는 객체를 두어, 시스템의 복잡성을 감추고 사용을 쉽게 만듭니다.

장점:

  • 서브시스템의 복잡성을 숨김
  • 서브시스템 간의 결합도를 낮춤

단점:

  • 퍼사드가 모든 서브시스템 클래스에 결합될 수 있음

퍼사드 패턴의 예시를 살펴보겠습니다:


// 복잡한 서브시스템
class CPU {
  freeze() { console.log('CPU: Freezing'); }
  jump(position) { console.log(`CPU: Jumping to ${position}`); }
  execute() { console.log('CPU: Executing'); }
}

class Memory {
  load(position, data) { console.log(`Memory: Loading ${data} to ${position}`); }
}

class HardDrive {
  read(lba, size) { console.log(`HardDrive: Reading ${size} bytes from ${lba}`); }
}

// 퍼사드
class ComputerFacade {
  constructor() {
    this.cpu = new CPU();
    this.memory = new Memory();
    this.hardDrive = new HardDrive();
  }

  start() {
    this.cpu.freeze();
    this.memory.load(0, 'boot data');
    this.cpu.jump(0);
    this.cpu.execute();
  }
}

// 클라이언트 코드
const computer = new ComputerFacade();
computer.start();

이 예제에서 ComputerFacade는 복잡한 컴퓨터 시스템의 시작 과정을 간단한 start() 메서드로 추상화합니다. 클라이언트는 복잡한 서브시스템을 직접 다루지 않고 퍼사드를 통해 쉽게 컴퓨터를 시작할 수 있습니다.

3.6 플라이웨이트 패턴 (Flyweight Pattern) 🪶

플라이웨이트 패턴은 많은 수의 유사한 객체를 효율적으로 관리하기 위한 패턴입니다. 이 패턴은 객체의 내부 상태를 공유 가능한 부분(intrinsic state)과 외부 상태(extrinsic state)로 분리하여, 메모리 사용을 최적화합니다.

장점:

  • 메모리 사용량 감소
  • 대량의 유사 객체 생성 시 성능 향상

단점:

  • 코드 복잡성 증가

플라이웨이트 패턴의 예시를 살펴보겠습니다:


// Flyweight
class Character {
  constructor(char, font, size) {
    this.char = char;
    this.font = font;
    this.size = size;
  }

  display(x, y) {
    console.log(`Character ${this.char} at (${x}, ${y}) with font ${this.font} and size ${this.size}`);
  }
}

// FlyweightFactory
class CharacterFactory {
  constructor() {
    this.characters = {};
  }

  getCharacter(char, font, size) {
    const key = char + font + size;
    if (!this.characters[key]) {
      this.characters[key] = new Character(char, font, size);
    }
    return this.characters[key];
  }

  getCount() {
    return Object.keys(this.characters).length;
  }
}

// 클라이언트 코드
const factory = new CharacterFactory();

const text = "Hello, Flyweight Pattern!";
const positions = [
  [0, 0], [10, 10], [20, 20], [30, 30], [40, 40]
];

for (let i = 0; i < text.length; i++) {
  const char = factory.getCharacter(text[i], 'Arial', 12);
  char.display(positions[i % positions.length][0], positions[i % positions.length][1]);
}

console.log(`Total Character objects created: ${factory.getCount()}`);

이 예제에서 CharacterFactory는 플라이웨이트 객체(Character)를 관리합니다. 동일한 문자, 폰트, 크기를 가진 Character 객체는 재사용되어 메모리 사용을 최적화합니다.

3.7 프록시 패턴 (Proxy Pattern) 🕵️

프록시 패턴은 다른 객체에 대한 대리자 또는 플레이스홀더를 제공하는 패턴입니다. 프록시는 원본 객체에 대한 접근을 제어하거나, 원본 객체의 생성과 초기화를 지연시키는 등의 역할을 수행할 수 있습니다.

장점:

  • 원본 객체에 대한 접근 제어
  • 지연 초기화를 통한 성능 향상

단점:

  • 코드의 복잡성 증가

프록시 패턴의 예시를 살펴보겠습니다:


// Subject
class Image {
  display() {}
}

// RealSubject
class RealImage extends Image {
  constructor(filename) {
    super();
    this.filename = filename;
    this.loadFromDisk();
  }

  loadFromDisk() {
    console.log(`Loading ${this.filename} from disk`);
  }

  display() {
    console.log(`Displaying ${this.filename}`);
  }
}

// Proxy
class ProxyImage extends Image {
  constructor(filename) {
    super();
    this.filename = filename;
    this.realImage = null;
  }

  display() {
    if (this.realImage === null) {
      this.realImage = new RealImage(this.filename);
    }
    this.realImage.display();
  }
}

// 클라이언트 코드
const image1 = new ProxyImage("image1.jpg");
const image2 = new ProxyImage("image2.jpg");

// image1만 로드되고 표시됨
image1.display();

// image2는 로드되지 않음
console.log("Image2 not loaded yet");

// image2 로드 및 표시
image2.display();

// image1 다시 표시 (이미 로드되어 있음)
image1.display();

이 예제에서 ProxyImageRealImage의 프록시 역할을 합니다. ProxyImageRealImage의 생성을 지연시키고, 필요할 때만 실제 이미지를 로드합니다. 이를 통해 불필요한 리소스 사용을 줄일 수 있습니다.

구조 패턴들은 객체들을 더 큰 구조로 조직화하는 다양한 방법을 제공합니다. 각 패턴은 특정 상황에서 유용하게 사용될 수 있으며, 코드의 구조를 개선하고 유지보수성을 높이는 데 도움을 줍니다.

다음 섹션에서는 행동 패턴에 대해 알아보겠습니다. 행동 패턴은 객체들 사이의 알고리즘과 책임 분배에 관련된 패턴입니다. 계속해서 자바스크립트 디자인 패턴의 세계를 탐험해봅시다! 🚀

4. 행동 패턴 (Behavioral Patterns) 🤝

행동 패턴은 객체들 사이의 알고리즘과 책임 분배에 관련된 패턴입니다. 이 패턴들은 객체 간의 상호작용을 어떻게 구성할지, 어떻게 통신할지에 대한 해결책을 제시합니다. 주요 행동 패턴으로는 책임 연쇄(Chain of Responsibility), 커맨드(Command), 인터프리터(Interpreter), 반복자(Iterator), 중재자(Mediator), 메멘토(Memento), 옵저버(Observer), 상태(State), 전략(Strategy), 템플릿 메서드(Template Method), 방문자(Visitor) 등이 있습니다.

4.1 책임 연쇄 패턴 (Chain of Responsibility Pattern) ⛓️

책임 연쇄 패턴은 요청을 처리할 수 있는 객체들의 체인을 따라 요청을 전달하는 패턴입니다. 각 객체는 요청을 처리할 수 있는지 판단하고, 처리할 수 없다면 다음 객체로 요청을 전달합니다.

장점:

  • 요청의 발신자와 수신자를 분리
  • 객체의 책임을 동적으로 추가/제거 가능

단점:

  • 요청이 처리되지 않을 수 있음

책임 연쇄 패턴의 예시를 살펴보겠습니다:


class Handler {
  constructor() {
    this.nextHandler = null;
  }

  setNext(handler) {
    this.nextHandler = handler;
    return handler;
  }

  handle(request) {
    if (this.nextHandler) {
      return this.nextHandler.handle(request);
    }
    return null;
  }
}

class ConcreteHandler1 extends Handler {
  handle(request) {
    if (request === 'request1') {
      return `Handler1: I'll handle the ${request}.`;
    }
    return super.handle(request);
  }
}

class ConcreteHandler2 extends Handler {
  handle(request) {
    if (request === 'request2') {
      return `Handler2: I'll handle the ${request}.`;
    }
    return super.handle(request);
  }
}

class ConcreteHandler3 extends Handler {
  handle(request) {
    if (request === 'request3') {
      return `Handler3: I'll handle the ${request}.`;
    }
    return super.handle(request);
  }
}

// 클라이언트 코드
const handler1 = new ConcreteHandler1();
const handler2 = new ConcreteHandler2();
const handler3 = new ConcreteHandler3();

handler1.setNext(handler2).setNext(handler3);

console.log(handler1.handle('request1'));
console.log(handler1.handle('request2'));
console.log(handler1.handle('request3'));
console.log(handler1.handle('request4'));

이 예제에서 각 핸들러는 특정 요청을 처리할 수 있으며, 처리할 수 없는 요청은 체인의 다음 핸들러로 전달됩니다.

4.2 커맨드 패턴 (Command Pattern) 🎮

커맨드 패턴은 요청을 객체의 형태로 캡슐화하여 매개변수화할 수 있게 해주는 패턴입니다. 이를 통해 요청을 큐에 저장하거나 로그로 기록하고, 작업을 취소하는 등의 기능을 구현할 수 있습니다.

장점:

  • 작업을 수행하는 객체와 작업을 요청하는 객체를 분리
  • 새로운 커맨드를 쉽게 추가 가능

단점:

  • 시스템에 많은 작은 커맨드 클래스들이 추가될 수 있음

커맨드 패턴의 예시를 살펴보겠습니다:


// Receiver
class Light {
  turnOn() {
    console.log('The light is on');
  }

  turnOff() {
    console.log('The light is off');
  }
}

// Command interface
class Command {
  execute() {}
}

// Concrete Commands
class TurnOnCommand extends Command {
  constructor(light) {
    super();
    this.light = light;
  }

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

class TurnOffCommand extends Command {
  constructor(light) {
    super();
    this.light = light;
  }

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

// Invoker
class RemoteControl {
  submit(command) {
    command.execute();
  }
}

// Client code
const light = new Light();
const turnOn = new TurnOnCommand(light);
const turnOff = new TurnOffCommand(light);
const remote = new RemoteControl();

remote.submit(turnOn);
remote.submit(turnOff);

이 예제에서 RemoteControl(Invoker)은 Light(Receiver)를 직접 조작하지 않고, Command 객체를 통해 간접적으로 조작합니다.

4.3 옵저버 패턴 (Observer Pattern) 👀

옵저버 패턴은 객체 간의 일대다 의존 관계를 정의하여, 한 객체의 상태가 변경되면 그 객체에 의존하는 모든 객체들이 자동으로 통지받고 갱신되는 방식을 구현합니다.

장점:

  • 느슨한 결합을 통한 유연성 증가
  • 상태 변경을 실시간으로 전파 가능

단점:

  • 복잡한 업데이트 순서나 순환 업데이트에 주의 필요

옵저버 패턴의 예시를 살펴보겠습니다:


class Subject {
  constructor() {
    this.observers = [];
  }

  addObserver(observer) {
    this.observers.push(observer);
  }

  removeObserver(observer) {
    const index = this.observers.indexOf(observer);
    if (index > -1) {
      this.observers.splice(index, 1);
    }
  }

  notify(data) {
    this.observers.forEach(observer => observer.update(data));
  }
}

class Observer {
  update(data) {}
}

class ConcreteSubject extends Subject {
  constructor() {
    super();
    this.state = null;
  }

  setState(state) {
    this.state = state;
    this.notify(this.state);
  }
}

class ConcreteObserver extends Observer {
  constructor(name) {
    super();
    this.name = name;
  }

  update(data) {
    console.log(`${this.name} received: ${data}`);
  }
}

// 클라이언트 코드
const subject = new ConcreteSubject();

const observer1 = new ConcreteObserver('Observer 1');
const observer2 = new ConcreteObserver('Observer 2');

subject.addObserver(observer1);
subject.addObserver(observer2);

subject.setState('New State');

이 예제에서 ConcreteSubject의 상태가 변경되면, 등록된 모든 ConcreteObserver들이 자동으로 업데이트를 받습니다.

4.4 상태 패턴 (State Pattern) 🔄

상태 패턴은 객체의 내부 상태가 변경될 때 객체의 행동이 변경되도록 허용하는 패턴입니다. 이 패턴을 사용하면 객체가 마치 클래스를 바꾸는 것처럼 보이게 할 수 있습니다.

장점:

  • 상태에 따른 동작을 별도의 클래스로 관리하여 코드 구조화
  • 새로운 상태 추가가 용이

단점:

  • 상태 클래스가 많아지면 관리가 복잡해질 수 있음

상태 패턴의 예시를 살펴보겠습니다:


class State {
  constructor(player) {
    this.player = player;
  }

  play() {}
  pause() {}
  stop() {}
}

class PlayingState extends State {
  play() {
    console.log('Already playing');
  }

  pause() {
    console.log('Pausing');
    this.player.setState(this.player.pausedState);
  }

  stop() {
    console.log('Stopping');
    this.player.setState(this.player.stoppedState);
  }
}

class PausedState extends State {
  play() {
    console.log('Resuming');
    this.player.setState(this.player.playingState);
  }

  pause() {
    console.log('Already paused');
  }

  stop() {
    console.log('Stopping');
    this.player.setState(this.player.stoppedState);
  }
}

class StoppedState extends State {
  play() {
    console.log('Starting playback');
    this.player.setState(this.player.playingState);
  }

  pause() {
    console.log('Can't pause when stopped');
  }

  stop() {
    console.log('Already stopped');
  }
}

class MusicPlayer {
  constructor() {
    this.playingState = new PlayingState(this);
    this.pausedState = new PausedState(this);
    this.stoppedState = new StoppedState(this);
    this.state = this.stoppedState;
  }

  setState(state) {
    this.state = state;
  }

  play() {
    this.state.play();
  }

  pause() {
    this.state.pause();
  }

  stop() {
    this.state.stop();
  }
}

// 클라이언트 코드
const player = new MusicPlayer();

player.play();  // Starting playback
player.pause(); // Pausing
player.play();  // Resuming
player.stop();  // Stopping
player.pause(); // Can't pause when stopped

이 예제에서 MusicPlayer의 행동은 현재 상태에 따라 다르게 동작합니다. 각 상태는 별도의 클래스로 관리되어 코드의 구조화와 유지보수가 용이해집니다.

4.5 전략 패턴 (Strategy Pattern) 🎯

전략 패턴은 알고리즘군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있게 만드는 패턴입니다. 이 패턴을 사용하면 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘을 변경할 수 있습니다.

장점:

  • 런타임에 알고리즘을 선택할 수 있는 유연성
  • 새로운 전략을 쉽게 추가 가능

단점:

  • 클라이언트가 적절한 전략을 선택해야 함

전략 패턴의 예시를 살펴보겠습니다:


// Strategy interface
class PaymentStrategy {
  pay(amount) {}
}

// Concrete Strategies
class CreditCardStrategy extends PaymentStrategy {
  constructor(name, cardNumber, cvv, dateOfExpiry) {
    super();
    this.name = name;
    this.cardNumber = cardNumber;
    this.cvv = cvv;
    this.dateOfExpiry = dateOfExpiry;
  }

  pay(amount) {
    console.log(`${amount} paid with credit card`);
  }
}

class PayPalStrategy extends PaymentStrategy {
  constructor(email, password) {
    super();
    this.email = email;
    this.password = password;
  }

  pay(amount) {
    console.log(`${amount} paid using PayPal`);
  }
}

// Context
class ShoppingCart {
  constructor() {
    this.items = [];
  }

  addItem(item) {
    this.items.push(item);
  }

  calculateTotal() {
    return this.items.reduce((total, item) => total + item.price, 0);
  }

  pay(paymentStrategy) {
    const amount = this.calculateTotal();
    paymentStrategy.pay(amount);
  }
}

// 클라이언트 코드
const cart = new ShoppingCart();
cart.addItem({name: 'Item 1', price: 100});
cart.addItem({name: 'Item 2', price: 50});

const creditCardStrategy = new CreditCardStrategy('John Doe', '0', '123', '12/2025');
const payPalStrategy = new PayPalStrategy('johndoe@example.com',

지적 재산권 보호

지적 재산권 보호 고지

  1. 저작권 및 소유권: 본 컨텐츠는 재능넷의 독점 AI 기술로 생성되었으며, 대한민국 저작권법 및 국제 저작권 협약에 의해 보호됩니다.
  2. AI 생성 컨텐츠의 법적 지위: 본 AI 생성 컨텐츠는 재능넷의 지적 창작물로 인정되며, 관련 법규에 따라 저작권 보호를 받습니다.
  3. 사용 제한: 재능넷의 명시적 서면 동의 없이 본 컨텐츠를 복제, 수정, 배포, 또는 상업적으로 활용하는 행위는 엄격히 금지됩니다.
  4. 데이터 수집 금지: 본 컨텐츠에 대한 무단 스크래핑, 크롤링, 및 자동화된 데이터 수집은 법적 제재의 대상이 됩니다.
  5. AI 학습 제한: 재능넷의 AI 생성 컨텐츠를 타 AI 모델 학습에 무단 사용하는 행위는 금지되며, 이는 지적 재산권 침해로 간주됩니다.

재능넷은 최신 AI 기술과 법률에 기반하여 자사의 지적 재산권을 적극적으로 보호하며,
무단 사용 및 침해 행위에 대해 법적 대응을 할 권리를 보유합니다.

© 2024 재능넷 | All rights reserved.

댓글 작성
0/2000

댓글 0개

해당 지식과 관련있는 인기재능

 운영하는 사이트 주소가 있다면 사이트를 안드로이드 앱으로 만들어 드립니다.기본 5000원은 아무런 기능이 없고 단순히 html 페이지를 로딩...

안녕하세요. 경력 8년차 프리랜서 개발자 입니다.피쳐폰 2g 때부터 지금까지 모바일 앱 개발을 전문적으로 진행해 왔으며,신속하 정확 하게 의뢰하...

미국석사준비중인 학생입니다.안드로이드 난독화와 LTE관련 논문 작성하면서 기술적인것들 위주로 구현해보았고,보안기업 개발팀 인턴도 오랜시간 ...

안녕하세요 안드로이드 개발 7년차에 접어든 프로그래머입니다. 간단한 과제 정도는 1~2일 안에 끝낼 수 있구요 개발의 난이도나 프로젝...

📚 생성된 총 지식 10,650 개

  • (주)재능넷 | 대표 : 강정수 | 경기도 수원시 영통구 봉영로 1612, 7층 710-09 호 (영통동) | 사업자등록번호 : 131-86-65451
    통신판매업신고 : 2018-수원영통-0307 | 직업정보제공사업 신고번호 : 중부청 2013-4호 | jaenung@jaenung.net

    (주)재능넷의 사전 서면 동의 없이 재능넷사이트의 일체의 정보, 콘텐츠 및 UI등을 상업적 목적으로 전재, 전송, 스크래핑 등 무단 사용할 수 없습니다.
    (주)재능넷은 통신판매중개자로서 재능넷의 거래당사자가 아니며, 판매자가 등록한 상품정보 및 거래에 대해 재능넷은 일체 책임을 지지 않습니다.

    Copyright © 2024 재능넷 Inc. All rights reserved.
ICT Innovation 대상
미래창조과학부장관 표창
서울특별시
공유기업 지정
한국데이터베이스진흥원
콘텐츠 제공서비스 품질인증
대한민국 중소 중견기업
혁신대상 중소기업청장상
인터넷에코어워드
일자리창출 분야 대상
웹어워드코리아
인터넷 서비스분야 우수상
정보통신산업진흥원장
정부유공 표창장
미래창조과학부
ICT지원사업 선정
기술혁신
벤처기업 확인
기술개발
기업부설 연구소 인정
마이크로소프트
BizsPark 스타트업
대한민국 미래경영대상
재능마켓 부문 수상
대한민국 중소기업인 대회
중소기업중앙회장 표창
국회 중소벤처기업위원회
위원장 표창