퍼사드 패턴
개요
영화를 틀려면 스크린을 내리고 조명을 낮추고 플레이어를 시작해야 한다. 버튼을 누르는 쪽이 매번 장치와 순서를 알아야 한다면 같은 준비 코드가 여러 화면에 퍼진다.
퍼사드(Facade)는 복잡한 하위 시스템 앞에 단순한 진입점을 세우는 패턴이다. 호출부는 영화 시작을 요청하고, 퍼사드가 그 작업에 필요한 하위 기능들을 연결한다.
UML
호출부는 MovieNight를 사용한다. MovieNight가 Screen, Lights, Player를 참조하고 시작·종료 시 필요한 순서로 호출한다.
역사
GoF는 1994년 Design Patterns에서 Facade를 구조 패턴으로 정리했다. 하위 시스템의 여러 인터페이스를 더 쓰기 쉬운 상위 인터페이스로 묶는 데 초점을 둔다.1
왜 사용하는가?
호출부는 개별 장치의 이름과 준비 순서를 몰라도 된다. 순서가 달라지면 여러 화면 대신 이 진입점에서 수정한다.
퍼사드는 진입점이지 쓰레기통이 아니다. 모든 업무 규칙을 직접 들고 있거나 세부 제어가 필요한 호출부까지 한 메서드로 묶으면 큰 객체만 남을 수 있다. 하위 기능의 책임은 해당 모듈에 둔다.
예제
영화 시작 과정을 메서드 하나로 묶는다. 장치는 실제 하드웨어 대신 수행할 동작의 이름을 반환한다.
using System;
using System.Collections.Generic;
using System.Linq;
var night = new MovieNight(new Screen(), new Lights(), new Player());
Console.WriteLine(night.Start("Moon"));
// 장치 하나의 동작과 여러 장치를 조정하는 순서를 구별한다.
sealed class Screen
{
public string Lower() => "screen down";
public string Raise() => "screen up";
}
sealed class Lights
{
public string Dim() => "lights dim";
public string Restore() => "lights normal";
}
sealed class Player
{
public string Play(string title) => "play " + title;
public string Stop() => "player stop";
}
// 호출부가 여러 장치와 순서를 외우지 않도록 준비 절차를 한곳에 둔다.
sealed class MovieNight(Screen screen, Lights lights, Player player)
{
public string Start(string title) =>
string.Join(" -> ", screen.Lower(), lights.Dim(), player.Play(title));
public string Stop() =>
string.Join(" -> ", player.Stop(), lights.Restore(), screen.Raise());
}
screen down -> lights dim -> play MoonStart()가 정한 순서대로 결과가 나열된다. 실제 장치를 제어하는 코드와 호출 순서의 책임은 구별되어 있다.
범위 넓힌 예제
종료 작업도 같은 퍼사드에 두고, 시작 후 종료까지 호출한다.
using System;
using System.Collections.Generic;
using System.Linq;
var night = new MovieNight(new Screen(), new Lights(), new Player());
Console.WriteLine(night.Start("Moon"));
Console.WriteLine(night.Stop());
// 장치 하나의 동작과 여러 장치를 조정하는 순서를 구별한다.
sealed class Screen
{
public string Lower() => "screen down";
public string Raise() => "screen up";
}
sealed class Lights
{
public string Dim() => "lights dim";
public string Restore() => "lights normal";
}
sealed class Player
{
public string Play(string title) => "play " + title;
public string Stop() => "player stop";
}
// 호출부가 여러 장치와 순서를 외우지 않도록 준비 절차를 한곳에 둔다.
sealed class MovieNight(Screen screen, Lights lights, Player player)
{
public string Start(string title) =>
string.Join(" -> ", screen.Lower(), lights.Dim(), player.Play(title));
public string Stop() =>
string.Join(" -> ", player.Stop(), lights.Restore(), screen.Raise());
}
screen down -> lights dim -> play Moon
player stop -> lights normal -> screen up한 메서드로 묶었다고 여러 장치의 작업이 원자적으로 성공하는 것은 아니다. 실제 장치라면 중간 실패를 어떻게 알리고 복구할지 따로 정해야 한다. Query Facade는 이 진입점 아이디어를 조회 결과 구성에 적용한 별도의 문서에서 다룬다.
TypeScript 예제
장치는 자기 동작을 맡고, MovieNight는 영화 시작과 종료에 필요한 호출 순서를 맡는다. 여러 화면이 같은 준비 순서를 복사하지 않으면서도 장치의 세부 책임은 원래 객체에 남는다.
// 장치 하나의 동작과 여러 장치를 조정하는 순서를 구별한다.
class Screen {
lower(): string { return "screen down"; }
raise(): string { return "screen up"; }
}
class Lights {
dim(): string { return "lights dim"; }
restore(): string { return "lights normal"; }
}
class Player {
play(title: string): string { return "play " + title; }
stop(): string { return "player stop"; }
}
// 호출부가 여러 장치와 순서를 외우지 않도록 준비 절차를 한곳에 둔다.
class MovieNight {
constructor(private readonly screen: Screen, private readonly lights: Lights, private readonly player: Player) {}
start(title: string): string {
return [this.screen.lower(), this.lights.dim(), this.player.play(title)].join(" -> ");
}
stop(): string {
return [this.player.stop(), this.lights.restore(), this.screen.raise()].join(" -> ");
}
}
const night = new MovieNight(new Screen(), new Lights(), new Player());
console.log(night.start("Moon"));
console.log(night.stop());screen down -> lights dim -> play Moon
player stop -> lights normal -> screen up반환 문자열은 수행 순서를 보여 주는 대역이다. 실제 장치를 제어하거나 여러 작업의 원자적 성공을 보장하지 않는다.
같이 보기
C# 예제는 각각 독립된 .NET 10 콘솔 프로그램이다.
TypeScript 예제는 ES2022 이상으로 컴파일한 JavaScript를 Node.js에서 실행한다.