Design PatternProgramming

퍼사드 패턴에 대해 알아보자

·8 min read

프로젝트가 점점 복잡해지고 있다.

복잡도를 생각하니 하나의 패턴이 떠오르는데,

바로 퍼사드 패턴이다.

Facade는 프랑스어로 건물의 정면(출입구)를 뜻한다.

우리가 건물 볼 때 복잡한 배수, 전기 배선 등의 구조를 모르고 정면에 입구만 봐도 건물의 용도가 이해되듯이

복잡한 것은 숨기고, 앞에는 단순한 간판만 내거는 개념이다.

퍼사드 패턴은 결합도와 상관이 있는데,

특정 기능을 위해 A, B, C, D 4개의 복잡한 클래스를 순서대로 호출해야할 때,

우리는 모든 클래스의 사용법을 알아야 하고, 만약 C 클래스의 이름이나 로직이 바뀌면, 호출 코드를 다 찾아 고쳐야한다.

이 경우 우리는 결합도가 높다고 판단한다.

만약에 A, B, C, D를 모두 아우르는 클래스가 있는 경우,

우리는 해당 클래스에 메소드를 호출하는 것으로 기능을 사용 가능하다.

이 경우 결합도가 낮다 또는 느슨하다고 하는데,

아무튼 그냥 호출 포인트를 통합하는 개념이다.

코드로 보자

예시로 클래스를 한번 통합해서 보여주겠다.

// Subsystem 1: TV
class TV {
    public void on() { System.out.println("TV: 전원이 켜졌습니다."); }
    public void setInput(String type) { System.out.println("TV: 입력이 " + type + "(으)로 설정되었습니다."); }
}

// Subsystem 2: Lighting
class Lighting {
    public void dim(int level) { System.out.println("조명: 밝기를 " + level + "%로 조절합니다."); }
}

// Subsystem 3: SoundSystem
class SoundSystem {
    public void on() { System.out.println("오디오: 전원이 켜졌습니다."); }
    public void setVolume(int level) { System.out.println("오디오: 볼륨을 " + level + "(으)로 설정합니다."); }
}

// Subsystem 4: StreamingApp
class StreamingApp {
    public void play(String movie) { System.out.println("앱: 영화 [" + movie + "] 재생을 시작합니다."); }
}

4개의 서브 시스템이 있고,

각 서브 시스템은 별도의 기능이 있다.

만약 서브 시스템마다 수십가지 기능이 있다면,

그 조합으로 만들어 낼 수 있는 기능은 무궁무진 하다.

다만 여기서 서브 시스템 하나가 바뀌어 버리면?

의존하는 모든 기능들에 영향이 끼친다.

그러니 서브 시스팀이 뭘 할 수 있는지가 아니라,

무슨 기능을 제공할 것인지를 묶어보자는 개념이다.

public class HomeTheaterFacade {
    // 1. 복잡한 서브 시스템들을 멤버 변수(private)로 숨김
    private TV tv;
    private Lighting light;
    private SoundSystem audio;
    private StreamingApp app;

    // 2. 생성자를 통해 외부에서 부품(객체)을 주입
    public HomeTheaterFacade(TV tv, Lighting light, SoundSystem audio, StreamingApp app) {
        this.tv = tv;
        this.light = light;
        this.audio = audio;
        this.app = app;
    }

    // 3. 사용자에게 제공할 단순한 버튼(메서드)
    public void watchMovie(String movieTitle) {
        System.out.println("=== 🍿 영화 관람 모드 시작 🍿 ===");
        light.dim(10);        // 조명 조절
        tv.on();              // TV 켜기
        tv.setInput("HDMI");  // 입력 설정
        audio.on();           // 오디오 켜기
        audio.setVolume(50);  // 볼륨 조절
        app.play(movieTitle); // 영화 재생
    }

    public void endMovie() {
        System.out.println("=== 🎬 영화 종료 ===");
        // 끄는 로직들...
    }
}

이렇게 까지 보면 사실 퍼사드도 별거 없어 보인다.

그냥 기능 묶어두면 퍼사드냐, 사실 그게 맞는 거 같다.

중요한건 클라이언트로 하여금 복잡한 것을 의식하지 않도록 하는게 개념이다 보니

정해진 방법이 있기 보단 목적이 있을 뿐이다.

만약 서브 시스템이나 기능 타겟이 바뀌면?

만약에 watchMovie를 TV가 아닌 넷플릭스에서 보고 싶다면?

파사드 자체를 인터페이스화 하면 된다.

public interface WatchFacade {
    void watchMovie(String movieTitle);
}

public class TVFacade implements WatchFacade {
    public void watchMovie(String movieTitle) {
        System.out.println("=== 🍿 TV 관람 모드 시작 🍿 ===");
    }
}

public class NetflixFacade implements WatchFacade {
    public void watchMovie(String movieTitle) {
        System.out.println("=== 🍿 Netflix 관람 모드 시작 🍿 ===");
    }
}

약간 전략 패턴의 영이기 때문에 여기서는 그냥 약식으로 표현하겠다.

접근은 엄격하게 유지하는게 좋다.

개발하다 파사드로 감싸있으면 한번쯤

"귀찮은데 서브 시스템 직접 호출할까?"라는 고민이 들 수 있는데

가급적 사용자에서 퍼사드만 접근하도록 막아두는게 권장 된다.

애초에 따로 접근 할 수 있게 열어 둘 꺼면, 퍼사드 패턴을 쓰는 의미가 없다.

또한 서브 시스템을 강제로 하면 AI 기반으로 코딩 할 때도 좋은데,

마치 레고를 주고 조립을 시키듯이 자기 맘대로 창의적인 삼각형 블록을 쓰는 일을 방지 할 수 있다.

우리가 제한해둔 코드 범위 내에서 놀게 시키는 것이다.

비슷한 애들끼리 차이점

중간에 뭐가 낀다는 측면에서 어댑터랑 프록시랑 비슷한데,

어댑터와는 목적성에 차이가 있다.

퍼사드는 복잡한 인터페이스나 호출 구조를 단순화 한다는 목적이 있고

어댑터는 서로 다른 인터페이스를 변환, 호환 시킨다는 목적이 있다.

프록시와는 개방성의 차이가 있는데

퍼사드는 뒤에 있는 서브 시스템을 감추는 것이 목적이다.

프록시는 원래 객체와 같은 인터페이스를 사용하지만 그 사이에 캐싱, 로깅 등의 기능을 욱여넣는게 목적이다.

퍼사드도 중첩 가능하다.

흔희들 말하는 GOD 객체,

모든 것을 싹 따 때려박은 퍼사드를 방지하기 위해

1차, 2차 등으로 퍼사드 안에서도 세부적인 퍼사드를 넣어 상위에서 호출하는 방식으로 구성하는 것이 권장된다.

물론 이정도 레벨의 프로덕트면 이미 구조화가 잘 되어있을테니 너무 걱정할 필요는 없을 것이다.

← Previous
멘토링 내용 공유해드림, 기획자 내용도 있음
Next →
AI 음성 기본