Facade Pattern의 개념과 적용

jibro·2025년 3월 18일

디자인패턴

목록 보기
1/1

소프트웨어 개발을 하다 보면 복잡한 시스템을 단순한 인터페이스로 감싸고 싶은 순간이 많습니다. 이러한 문제를 해결하는 디자인 패턴이 바로 퍼사드 패턴(Facade Pattern)입니다. 하지만, 무조건 적용한다고 좋은 것은 아니며, 특정 상황에서는 오히려 코드가 복잡해질 수 있습니다. 이번 글에서는 퍼사드 패턴의 개념과 사용 시 고려해야 할 부분, 그리고 퍼사드 패턴을 피하거나 리팩토링하는 방법까지 정리해보겠습니다.


퍼사드 패턴(Facade Pattern) 개념

퍼사드 패턴은 복잡한 서브시스템을 단순한 인터페이스로 감싸는 디자인 패턴입니다. 이를 통해 클라이언트가 내부 구현을 신경 쓰지 않고, 단순한 API를 통해 기능을 사용할 수 있도록 합니다.

  • 복잡한 내부 시스템을 감춘다.

  • 클라이언트에게 단순한 인터페이스를 제공한다.

  • 유지보수성과 확장성을 높인다.

  • 내부 구현이 변경되어도 클라이언트 코드에 영향을 주지 않는다.

// 서브 시스템 (각 장치별 개별 동작)
class Lights {
  turnOn() { console.log("조명을 켭니다."); }
  turnOff() { console.log("조명을 끕니다."); }
}

class Projector {
  turnOn() { console.log("프로젝터를 켭니다."); }
  turnOff() { console.log("프로젝터를 끕니다."); }
}

class SoundSystem {
  turnOn() { console.log("사운드 시스템을 켭니다."); }
  turnOff() { console.log("사운드 시스템을 끕니다."); }
}

class BluRayPlayer {
  turnOn() { console.log("블루레이 플레이어를 켭니다."); }
  play() { console.log("영화를 재생합니다."); }
  turnOff() { console.log("블루레이 플레이어를 끕니다."); }
}

// 퍼사드 (Facade) 클래스
class HomeTheaterFacade {
  constructor(
    private lights: Lights,
    private projector: Projector,
    private soundSystem: SoundSystem,
    private bluRayPlayer: BluRayPlayer
  ) {}

  watchMovie() {
    console.log("\n📽️ 영화 감상을 시작합니다...");
    this.lights.turnOff();
    this.projector.turnOn();
    this.soundSystem.turnOn();
    this.bluRayPlayer.turnOn();
    this.bluRayPlayer.play();
  }

  stopMovie() {
    console.log("\n⏹️ 영화 감상을 종료합니다...");
    this.bluRayPlayer.turnOff();
    this.soundSystem.turnOff();
    this.projector.turnOff();
    this.lights.turnOn();
  }
}

// 사용 예시
const homeTheater = new HomeTheaterFacade(
  new Lights(), new Projector(), new SoundSystem(), new BluRayPlayer()
);

homeTheater.watchMovie(); // 🎬 영화 감상 시작!
homeTheater.stopMovie();  // ⏹️ 영화 종료!

이제 클라이언트는 내부 시스템을 몰라도 watchMovie()만 호출하면 됩니다.

퍼사드 패턴과 계층형 아키텍처

퍼사드 패턴은 어디에서 적합할까?

퍼사드 패턴은 서비스(Service) 계층에서 활용하는 것이 가장 적합합니다. 이는 퍼사드가 여러 하위 모듈(Repository, External API, 서브 시스템 등)을 조합하여 단순한 인터페이스를 제공하기 때문입니다.

계층형 아키텍처에서 퍼사드 패턴의 위치

Controller (사용자 요청 처리)

Service (비즈니스 로직 및 퍼사드 패턴 적용)

Repository (데이터 접근 계층)

  • Controller: 클라이언트 요청을 받고, 적절한 서비스 메서드를 호출

  • Service (퍼사드 적용 가능): 여러 개의 Repository 및 서브시스템을 조합하여 단순한 API 제공

  • Repository: 데이터베이스 또는 외부 API와 직접 통신

퍼사드 패턴 적용 예시 (Service 계층에서 활용)

class OrderRepository {
  findOrders(userId: string) {
    console.log(`🔍 사용자 ${userId}의 주문 내역 조회`);
  }
}

class PaymentRepository {
  processPayment(orderId: string) {
    console.log(`💳 주문 ${orderId}의 결제 처리`);
  }
}

// 퍼사드 적용 - Service 계층에서 여러 Repository를 조합하여 단순한 API 제공
class OrderService {
  constructor(
    private orderRepo: OrderRepository,
    private paymentRepo: PaymentRepository
  ) {}

  placeOrder(userId: string, orderId: string) {
    console.log("🛒 주문 처리 시작...");
    this.orderRepo.findOrders(userId);
    this.paymentRepo.processPayment(orderId);
    console.log("✅ 주문 완료!");
  }
}

// 사용 예시
const orderService = new OrderService(new OrderRepository(), new PaymentRepository());
orderService.placeOrder("user123", "order456");

Service 계층에서 퍼사드 패턴을 적용하면, Controller가 단순한 메서드 호출만 하면 되므로 클린한 구조를 유지할 수 있습니다

퍼사드 패턴 사용 시 고려해야할 사항

1) 내부 시스템이 단순한 경우

퍼사드를 적용하지 않아도 클라이언트가 쉽게 사용할 수 있는 코드라면, 굳이 감쌀 필요가 없습니다.

class Database {
  query(sql: string) {
    console.log(`Executing SQL: ${sql}`);
  }
}

// 퍼사드 없이 직접 사용
const db = new Database();
db.query("SELECT * FROM users;");

단순한 시스템에서는 불필요한 계층을 추가하지 않는 것이 더 좋습니다.

2) 퍼사드 클래스가 너무 많은 역할을 하는 경우 (“God Object” 문제)

퍼사드가 너무 많은 기능을 포함하면, 하나의 거대한 클래스로 변질될 수 있습니다.

class ECommerceFacade {
  private userService = new UserService();
  private orderService = new OrderService();
  private paymentService = new PaymentService();

  handleUserAndOrders() {
    this.userService.getUser();
    this.orderService.getOrders();
  }

  processUserPayment() {
    this.userService.getUser();
    this.paymentService.processPayment();
  }
}

서브시스템 단위로 역할을 분리하는 것이 더 좋습니다.

class UserFacade { /* 사용자 관련 기능만 */ }
class OrderFacade { /* 주문 관련 기능만 */ }
class PaymentFacade { /* 결제 관련 기능만 */ }

3) 확장성이 필요한 경우

퍼사드가 너무 제한적인 경우, 클라이언트가 특정 기능을 사용하고 싶어도 제한될 수 있습니다.

class NotificationFacade {
  private notificationService = new NotificationService();

  sendNotification() {
    this.notificationService.sendEmail();
    this.notificationService.sendSMS();
  }
}

클라이언트가 개별 서비스를 직접 사용할 수 있도록 옵션을 추가하면 유연성이 증가합니다.

class NotificationFacade {
  constructor(private notificationService: NotificationService) {}
  sendAllNotifications() {
    this.notificationService.sendEmail();
    this.notificationService.sendSMS();
  }
}

const notificationService = new NotificationService();
notificationService.sendEmail(); // 직접 이메일 전송 가능

결론

퍼사드 패턴은 강력한 패턴이지만, 모든 경우에 적합한 것은 아닙니다. 아래의 사항을 잘 고려하는 것이 적재적소에 잘 사용하는 방법입니다.

사용할 때 고려해야할 점:

  • 단순한 시스템에서는 불필요한 계층을 추가하지 않는다.

  • 너무 많은 역할을 하는 “God Object”가 되지 않도록 한다.

  • 확장성을 고려하여 클라이언트가 개별 서비스를 직접 사용할 수 있도록 한다.

profile
To Infinity and Beyond

0개의 댓글