[디자인 패턴] (GoF 구조패턴) Facade Pattern에 대하여
안녕하세요! GoF 디자인 패턴 연재, 이번 시간에는 구조 패턴(Structural Patterns)의 한 종류로, 복잡한 서브시스템을 더 쉽게 사용할 수 있도록 간단한 통합 인터페이스를 제공하는 퍼사드(Facade) 패턴에 대해 알아보겠습니다.
정의
퍼사드(Facade)는 프랑스어로 '건물의 정면'을 의미합니다. 퍼사드 패턴은 이 이름처럼, 복잡하게 얽혀있는 서브시스템들의 정면에 서서, 간단하고 통일된 하나의 창구(인터페이스)를 제공하는 패턴입니다. 클라이언트는 이 퍼사드 객체만 상대하면 되므로, 내부의 복잡한 구조를 알 필요가 없습니다.
가장 좋은 예는 '홈시어터' 시스템입니다. 영화 한 편을 보기 위해 우리는 앰프, DVD 플레이어, 프로젝터, 스크린, 조명 등 수많은 기기를 조작해야 합니다. "앰프 켜기 -> DVD 플레이어 켜기 -> DVD 넣기 -> 프로젝터 켜기 -> 스크린 내리기 -> 조명 어둡게 하기" 와 같은 복잡한 절차를 매번 거쳐야 하죠. 이때 '홈시어터 통합 리모컨'이 있다면 어떨까요? "영화 보기" 버튼 하나만 누르면, 이 모든 과정이 알아서 착착 진행됩니다. 여기서 '홈시어터 통합 리모컨'이 바로 퍼사드 역할을 합니다. 클라이언트(사용자)는 복잡한 내부 동작을 전혀 몰라도, "영화 보기"라는 단순한 인터페이스를 통해 원하는 결과를 얻을 수 있습니다.
퍼사드 패턴의 구조는 매우 직관적입니다.
- Facade (퍼사드): 서브시스템의 복잡한 기능들을 조합하여 클라이언트가 사용하기 쉬운 상위 수준의 인터페이스를 제공합니다. 클라이언트의 요청을 받으면, 자신이 알고 있는 여러 서브시스템 객체들에게 작업을 위임합니다.
- Subsystem Classes (서브시스템 클래스들): 실제 기능을 구현하는 여러 클래스의 집합입니다. 이들은 퍼사드 객체의 존재를 전혀 알지 못합니다.
- Client (클라이언트): Facade 객체를 통해 서브시스템의 기능들을 사용합니다.
사용예시
앞서 설명한 홈시어터 예제를 자바(Java) 코드로 구현해 보겠습니다.
1. 서브시스템 (Subsystem) 클래스들 정의
홈시어터를 구성하는 각 기기들을 클래스로 정의합니다.
// Subsystem A
class Amplifier {
public void on() { System.out.println("앰프가 켜졌습니다."); }
public void setDvd() { System.out.println("앰프를 DVD 모드로 설정합니다."); }
public void setSurroundSound() { System.out.println("앰프를 서라운드 사운드로 설정합니다."); }
public void setVolume(int level) { System.out.println("볼륨을 " + level + "로 설정합니다."); }
public void off() { System.out.println("앰프가 꺼졌습니다."); }
}
// Subsystem B
class DvdPlayer {
public void on() { System.out.println("DVD 플레이어가 켜졌습니다."); }
public void play(String movie) { System.out.println("\"" + movie + "\"를 재생합니다."); }
public void off() { System.out.println("DVD 플레이어가 꺼졌습니다."); }
}
// Subsystem C
class Projector {
public void on() { System.out.println("프로젝터가 켜졌습니다."); }
public void wideScreenMode() { System.out.println("프로젝터를 와이드 스크린 모드로 설정합니다."); }
public void off() { System.out.println("프로젝터가 꺼졌습니다."); }
}
// ... 그 외 Screen, TheaterLights 등 다른 서브시스템 클래스들 ...
2. 퍼사드 (Facade) 클래스 구현
복잡한 과정을 하나로 묶어주는 HomeTheaterFacade를 정의합니다.
// Facade 클래스
public class HomeTheaterFacade {
// 서브시스템 객체들을 구성(composition)으로 가짐
private Amplifier amp;
private DvdPlayer dvd;
private Projector projector;
public HomeTheaterFacade(Amplifier amp, DvdPlayer dvd, Projector projector) {
this.amp = amp;
this.dvd = dvd;
this.projector = projector;
}
// 복잡한 과정을 하나로 묶어 단순한 인터페이스를 제공
public void watchMovie(String movie) {
System.out.println("=== 영화 볼 준비를 시작합니다... ===");
projector.on();
projector.wideScreenMode();
amp.on();
amp.setDvd();
amp.setSurroundSound();
amp.setVolume(5);
dvd.on();
dvd.play(movie);
}
public void endMovie() {
System.out.println("\n=== 홈시어터를 끕니다... ===");
dvd.off();
amp.off();
projector.off();
}
}
3. 클라이언트 (Client) 코드
클라이언트는 퍼사드 객체만 사용하여 간단하게 홈시어터를 조작합니다.
public class Client {
public static void main(String[] args) {
// 1. 필요한 서브시스템 객체들 생성
Amplifier amp = new Amplifier();
DvdPlayer dvd = new DvdPlayer();
Projector projector = new Projector();
// 2. 퍼사드 객체 생성 시 서브시스템들을 전달
HomeTheaterFacade homeTheater = new HomeTheaterFacade(amp, dvd, projector);
// 3. 클라이언트는 단순한 메서드만 호출
homeTheater.watchMovie("인셉션");
homeTheater.endMovie();
}
}
결론
그럼 퍼사드 패턴은 언제 사용하면 좋을까요?
- 복잡한 서브시스템에 대해 간단한 인터페이스를 제공하고 싶을 때: 클라이언트와 서브시스템 간의 의존성을 줄여 결합도를 낮출 수 있습니다.
- 서브시스템을 계층적으로 구성해야 할 때: 각 계층은 퍼사드를 통해 다음 하위 계층과 통신하도록 구성할 수 있습니다.
- 레거시 시스템을 리팩토링할 때: 기존의 복잡한 코드를 직접 건드리지 않고, 퍼사드를 통해 새로운 인터페이스를 제공하여 점진적으로 시스템을 개선할 수 있습니다.
Client에게 사편의성을 줄 수 있는 패턴이지만 다음과 같은 항목을 조심해야합니다
- 퍼사드 객체가 모든 책임을 지는 만능 객체가 될 수 있음: 너무 많은 기능을 퍼사드에 추가하면, 퍼사드 자체가 또 다른 복잡한 서브시스템이 되어버릴 수 있습니다. (God Object 안티 패턴)
- 불필요한 의존성: 클라이언트가 서브시스템의 일부 기능만 필요로 하더라도, 퍼사드를 통해 전체 서브시스템과 간접적으로 의존하게 될 수 있습니다.
퍼사드 패턴은 복잡함을 감추고 단순함을 드러내는 매우 실용적인 패턴입니다. 잘 설계된 퍼사드는 시스템의 사용성을 크게 향상시키고, 유지보수를 용이하게 만들어줍니다. 다음 시간에는 마지막 구조패턴, Proxy 패턴에 대해서 알아보겠습니다.