TIL

JavaScript 다양한 디자인 패턴 (간단하게)

추운날_너를_기다리며 2024. 9. 26. 22:39

1. Factory Pattern

  • 요약: 객체 생성 로직을 별도의 메서드로 캡슐화하여, 객체 생성 과정을 쉽게 관리할 수 있는 패턴입니다. 객체를 생성할 때 직접 클래스를 호출하지 않고, 팩토리 메서드를 통해 객체를 반환받습니다.
  • 왜 사용하는지?: 객체 생성 방식이 복잡하거나, 여러 종류의 객체를 유연하게 생성해야 할 때 사용합니다.
  • 예시:
class Car {
  constructor(brand) {
    this.brand = brand;
  }
  drive() {
    console.log(`${this.brand} is driving`);
  }
}

class CarFactory {
  static createCar(type) {
    switch (type) {
      case 'Tesla':
        return new Car('Tesla');
      case 'BMW':
        return new Car('BMW');
      default:
        return new Car('Generic Car');
    }
  }
}

const myCar = CarFactory.createCar('Tesla');
myCar.drive(); // Tesla is driving

2. Singleton Pattern

  • 요약 : 특정 클래스의 인스턴스가 하나만 생성되도록 보장하는 패턴입니다. 애플리케이션 전체에서 하나의 객체를 공유해야 할 때 유용합니다.
  • 왜 사용하는지?: 전역적인 상태 관리나, 하나의 객체로 모든 곳에서 동일한 인스턴스를 참조해야 할 때 사용합니다.
  • 예시:
class Singleton {
  constructor() {
    if (Singleton.instance) {
      return Singleton.instance;
    }
    this.data = [];
    Singleton.instance = this;
  }

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

  getData() {
    return this.data;
  }
}

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

instance1.addData('Item 1');
console.log(instance2.getData()); // ['Item 1'] -> 두 인스턴스가 동일 객체를 참조

3. Observer Pattern

  • 요약 : 객체 간의 의존성을 정의하여, 한 객체의 상태 변화가 다른 객체에게 자동으로 전파되도록 만드는 패턴입니다. Publisher-Subscriber 모델로도 불립니다.
  • 왜 사용하는지?: 객체 상태 변화가 발생할 때, 그 변화를 다른 객체들에게 통지하고 싶을 때 사용합니다. 예를 들어 이벤트 시스템에 많이 사용됩니다.
  • 예시:
class Subject {
  constructor() {
    this.observers = [];
  }

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

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

class Observer {
  update(data) {
    console.log(`Received update: ${data}`);
  }
}

const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();

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

subject.notify('New Data'); // 모든 관찰자가 업데이트 받음

4. Strategy Pattern

  • 요약 : 여러 알고리즘을 정의하고, 런타임에 하나의 알고리즘을 선택해서 사용하는 패턴입니다. 전략(Strategy)들을 캡슐화하여, 클라이언트 코드가 특정 전략에 종속되지 않도록 합니다.
  • 왜 사용하는지?: 동일한 작업을 수행하지만 다양한 방식(알고리즘)을 제공해야 할 때 사용됩니다.
  • 예시:
class PaymentStrategy {
  pay(amount) {
    throw new Error('This method should be overridden');
  }
}

class CreditCardPayment extends PaymentStrategy {
  pay(amount) {
    console.log(`Paid ${amount} using Credit Card`);
  }
}

class PayPalPayment extends PaymentStrategy {
  pay(amount) {
    console.log(`Paid ${amount} using PayPal`);
  }
}

class PaymentProcessor {
  constructor(strategy) {
    this.strategy = strategy;
  }

  process(amount) {
    this.strategy.pay(amount);
  }
}

const creditCardPayment = new PaymentProcessor(new CreditCardPayment());
creditCardPayment.process(100); // Paid 100 using Credit Card

const payPalPayment = new PaymentProcessor(new PayPalPayment());
payPalPayment.process(200); // Paid 200 using PayPal

5. Decorator Pattern

  • 요약 : 기존 객체의 기능을 확장할 때 상속을 사용하지 않고, 객체를 동적으로 감싸서 새로운 기능을 추가하는 패턴입니다.
  • 왜 사용하는지?: 객체의 기능을 변경하거나 확장할 때 원본 객체를 수정하지 않고 기능을 추가하고 싶을 때 사용합니다.
  • 예시:
class Coffee {
  cost() {
    return 5;
  }
}

class MilkDecorator {
  constructor(coffee) {
    this.coffee = coffee;
  }

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

class SugarDecorator {
  constructor(coffee) {
    this.coffee = coffee;
  }

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

let myCoffee = new Coffee();
myCoffee = new MilkDecorator(myCoffee);
myCoffee = new SugarDecorator(myCoffee);

console.log(`Total cost: $${myCoffee.cost()}`); // Total cost: $8

 

6. Repository Pattern

  • 요약: 데이터 접근 로직을 비즈니스 로직과 분리하여, 데이터베이스와의 상호작용을 관리하는 패턴입니다. 데이터 접근과 관련된 CRUD(Create, Read, Update, Delete) 연산을 Repository 객체에서 처리함으로써 코드의 유지보수성과 테스트 가능성을 높입니다.
  • 왜 사용하는지 ?: 데이터베이스 관련 로직을 비즈니스 로직에서 분리하여 모듈화하고, 데이터베이스나 스토리지 시스템을 유연하게 변경할 때 사용합니다.
  • 예시:
class UserRepository {
  constructor(databaseConnection) {
    this.db = databaseConnection;
  }

  async findById(id) {
    const result = await this.db.query('SELECT * FROM users WHERE id = ?', [id]);
    return new User(result.id, result.name, result.email);
  }

  async save(user) {
    await this.db.query('INSERT INTO users (name, email) VALUES (?, ?)', [user.name, user.email]);
  }
}

class UserService {
  constructor(userRepository) {
    this.userRepository = userRepository;
  }

  async getUserInfo(userId) {
    return await this.userRepository.findById(userId);
  }

  async createUser(name, email) {
    const newUser = new User(null, name, email);
    await this.userRepository.save(newUser);
  }
}

오늘의 배움

오늘은 Factory Pattern, Singleton Pattern, Observer Pattern, Strategy Pattern, Decorator Pattern, Repository Pattern 과 같은 다양한 디자인 패턴들을 학습했습니다. 각 패턴은 다양한 상황에서 코드의 확장성과 유연성을 높여주며, 상황에 맞게 적절히 사용함으로써 더 견고한 애플리케이션을 개발할 수 있다는 점을 깨달았습니다.