
퍼사드 패턴은 사용하기 복잡한 클래스 라이브러리에 대해 사용하기 편하게 간단한 인터페이스를 구성하기 위한 구조 패턴이다. 예를 들어 라이브러리의 각 클래스와 메서드들이 어던 목적의 동작인지 이해하기 어려워 바로 가져다 쓰기에는 난이도가 높을때, 이에 대한 적절한 네이밍과 정리를 통해 사용자로 하여금 쉽게 라이브러리를 다룰 수 있도록 인터페이스를 만드는데, 우리가 교재를 보고 필기노트에 재요약을 하듯이 클래스를 재정리하는 행위로 보면 된다.
본래 프로그램이라는 것은 업데이트를 통해 점점 커지게 된다. 버전이 올라갈 수록 많은 클래스들이 만들어져 서로 관계를 맺으면서 점점 복잡해지게 된다. 그래서 커다란 솔루션을 구성하려면 상호 관련된 많은 클래스들을 적절히 제어해야 할 필요성이 있다. 이 때 이 처리를 개별적으로 처리하는 것이 아닌 일종의 '창구'를 준비하여 중계할 수 있도록 구성해주면, 사용자는 창구를 통해서 간단한 명령 요구만 내리면 요구에 대해 필요한 모든 집약적 행위들을 창구가 알아서 처리해 결과를 내준다.

이처럼 퍼사드 패턴은 복잡하게 얽혀 있는 클래스를 정리해서 사용하기 편한 인터페이스를 유저에게 제공한다고 보면 된다. 유저는 복잡한 내부 시스템이나 클래스를 알 필요없이 외부에서 제공된 인터페이스를 이용하기만 하면 된다. 파사드를 이용하면 자칫 동작의 목적과 같은 중요한 사항을 놓치는 실수를 줄일 수 있다.
Facade라는 단어의 뜻은 건축물의 정면을 의미한다. 건축물의 정면은 보통 건축물의 이미지와 건축 의도를 나타내기 때문에 오래 전부터 특별한 디자인을 적용하여 의미를 부여했다. 이처럼 건축물 정면만 봐도 이 건물의 목적을 단번에 알 수 있다는 특징을 차용해서 퍼사드 패턴이라고 명명한 것이다.

퍼사드 패턴은 전략 패턴이나 팩토리 패턴과 다르게 클래스 구조가 정형화 되지 않은 패턴이다. 반드시 클래스 위치는 어떻고 어떤 형식으로 위임을 해야한다고 정해진 것이 없다. 그냥 퍼사드 클래스를 만들어 적절히 기능 집약화만 해주면 그게 디자인 패턴이 된다.
C언어나 파이썬에서 복잡한 로직의 코드가 있으면 이걸 main 함수에서 모두 실행하는 것이 아니라 함수 분리를 통해 main 함수의 코드를 심플하게 구성해본 경험이 있을 것이다. 이를 객체 지향 프로그래밍 관점으로 치환한 것이 퍼사드 패턴이다. 즉, 퍼사드는 복잡한 것을 단순하게 보여주는 것에 초점을 둔다.

Additional Facade를 재귀적 퍼사드라고도 말한다. 예를들어 다수의 패키지를 포함하고 있는 큰 싯트메에 요소 요소마다 Facade 패턴을 적용하고 다시 그 Facade를 합친 Facade를 만드는 식으로, 퍼사드를 재귀적으로 구성하면 시스템은 보다 편리하게 된다. 이처럼 퍼사드는 한개만 있으라는 법은 없고 필요에 의하면 얼마든지 늘릴 수 있다.

외부에서 내부 로직을 직접 사용하기 때문에 내부 로직의 구조를 변경한다고 하거나 파라미터나 리턴 값을 변경할 경우 직접적으로 영향을 받아 수정이 불가능한 경우가 종종 있다.하지만 중간에 매개체 역할을 해주는 퍼사드 객체가 있어 실제 내부 로직이 어떻게 변경이 되더라도 상관이 없어지므로 의존성이 감소된다.
인게임에서 장비의 종류가 많아 데이터베이스를 별도로 운용하고 있다. 이 때 장비 데이터베이스로부터 데이터를 조회해서 출력해주는 패키지가 있다고 해보자. 우리는 이 라이브러리를 이용해서 데이터베이스로 부터 값을 얻어오고 데이터를 파싱해서 출력하는 프로그램을 만들려고 한다. 패키지에는 총 4개의 Cache, DBMS, Row, Message 클래스가 존재한다.

그런데 이 라이브러리를 이용하는데 있어 데이터베이트를 조회해서 데이터를 가공하기까지 다음과 같은 규칙이 존재한다.
DBMS에서 한 번이라도 조회된 데이터는 성능을 위해 반드시 캐시에 저장해야 한다는 것을 잊지 말아야하고, 데이터 가공을 위해서는 Message 클래스를 써야한다. 이 수칙을 따르지 않으면 라이브러리가 제대로 작동하지 않아 개발자는 위의 사항을 제대로 숙지한 상태에서 프로그래밍을 하여야한다.
class EquipmentData {
private string name;
private int physicalDamage;
private int magicalDamage;
private float attackSpeed;
public int getPhysicalDamage()
{
return this.physicalDamage;
}
public int getMagicalDamage()
{
return this.magicalDamage;
}
public float getAttackSpeed()
{
return this.attackSpeed;
}
public EquipmentData(string name, int physicalDamage, int magicalDamage, float attackSpeed) {
this.name = name;
this.physicalDamage = physicalDamage;
this.magicalDamage = magicalDamage;
this.attackSpeed = attackSpeed;
}
}
// 데이터 베이스 역할을 하는 클래스
public class DBMS
{
private Dictionary<string, EquipmentData> db = new Dictionary<string, EquipmentData>();
public void Put(string name, EquipmentData equipment)
{
db[name.ToLower()] = equipment;
}
// 데이터베이스에 쿼리를 날려 결과를 받아오는 메소드
public EquipmentData Query(string name)
{
try
{
Thread.Sleep(500); // DB 조회 시간을 비유하여 0.5초 대기로 구현
}
catch (ThreadInterruptedException) {}
db.TryGetValue(name.ToLower(), out var equipment);
return equipment;
}
}
// DBMS에서 조회된 데이터를 임시로 담아두는 클래스 *속도향상*
public class Cache
{
private Dictionary<string, EquipmentData> cache = new Dictionary<string, EquipmentData>();
public void Put(EquipmentData equipment)
{
cache[equipment.getName().ToLower()] = equipment;
}
public EquipmentData Get(string name)
{
cache.TryGetValue(name.ToLower(), out var equipment);
return equipment;
}
}
// EquipmentData를 출력
public class Message
{
private EquipmentData equipment;
public Message(EquipmentData equipment)
{
this.equipment = equipment;
}
public string MakeName()
{
return "Name : \"" + equipment.getName() + "\"";
}
public string MakePhysicalDamage()
{
return "Physical Damage : " + equipment.getPhysicalDamage();
}
public string MakeMagicalDamage()
{
return "Magical Damage : " + equipment.getMagicalDamage();
}
public string MakeAttackSpeed()
{
return "Attack Speed : " + equipment.getAttackSpeed();
}
}
당연하게도 라이브러리의 코드를 메인 로직에 작성해서 구현한다는 문제가 아래 같이 발생한다. 데이터를 조회하고 출력되기까지 여러 개의 객체가 사용되고 있다. 물론 당장은 프로그램이 정상적으로 돌아가 서비스에 문제는 없을지도 모르지만, 나중에 수정 및 확장에 있어 수칙들을 까먹고 실수를해서 서비스에 버그가 생길 수도 있다.
public class EquipmentManager : MonoBehaviour
{
void Start()
{
// 1. 데이터베이스 생성 & 등록
DBMS dbms = new DBMS();
dbms.Put("Sword", new EquipmentData("Sword", 50, 20, 1.5f));
dbms.Put("Staff", new EquipmentData("Staff", 10, 60, 1.2f));
dbms.Put("Dagger", new EquipmentData("Dagger", 30, 10, 2.0f));
// 2. 캐시 생성
Cache cache = new Cache();
// 3. 트랜잭션에 앞서 먼저 캐시에 데이터가 있는지 조회
string name = "Sword";
EquipmentData equipment = cache.Get(name);
// 4. 만약 캐시에 없다면
if (equipment == null)
{
equipment = dbms.Query(name); // DB에 해당 데이터를 조회해서 equipment에 저장하고
if (equipment != null)
{
cache.Put(equipment); // 캐시에 저장
}
}
// 5. dbms.Query(name)에서 조회된 값이 있으면
if (equipment != null)
{
Message message = new Message(equipment);
Debug.Log(message.MakeName());
Debug.Log(message.MakePhysicalDamage());
Debug.Log(message.MakeMagicalDamage());
Debug.Log(message.MakeAttackSpeed());
}
// 6. 조회된 값이 없으면
else
{
Debug.Log($"{name} 가 데이터베이스에 존재하지 않습니다.");
}
}
}

따라서 이러한 사항들을 개발자가 직접 기억해서 하나하나 따져가면 코드를 작성하는 것보다 하나로 묶은 클래스를 하나 추가해서 단순화된 인터페이스를 통해 서브 클래스를 다룸으로써 개발자의 실수를 줄이고자 하는 것이 퍼사드 패턴이다.

다음과 같이 퍼사드 클래스드를 생성해주고 메인 메소드의 로직을 퍼사드 클래스의 메서드에 넣는다. (분리가 필요하면 메서드를 더 늘린다.)
// Facade 패턴 클래스
public class EquipmentFacade
{
private DBMS dbms = new DBMS();
private Cache cache = new Cache();
// 장비 데이터를 데이터베이스에 넣는 메소드
public void InsertEquipmentData()
{
dbms.Put("Sword", new EquipmentData("Sword", 50, 20, 1.5f));
dbms.Put("Staff", new EquipmentData("Staff", 10, 60, 1.2f));
dbms.Put("Dagger", new EquipmentData("Dagger", 30, 10, 2.0f));
}
// 장비 데이터를 캐시에서 먼저 확인하고 없으면 DB에서 가져오는 메소드
public void QueryAndDisplayEquipmentData(string name)
{
EquipmentData equipment = cache.Get(name);
// 1. 만약 캐시에 없다면
if (equipment == null)
{
equipment = dbms.Query(name); // DB에 해당 데이터를 조회
if (equipment != null)
{
cache.Put(equipment); // 캐시에 저장
}
}
// 2. dbms.Query(name)에서 조회된 값이 있으면
if (equipment != null)
{
Message message = new Message(equipment);
Debug.Log(message.MakeName());
Debug.Log(message.MakePhysicalDamage());
Debug.Log(message.MakeMagicalDamage());
Debug.Log(message.MakeAttackSpeed());
}
// 3. 조회된 값이 없으면
else
{
Debug.Log($"{name} 가 데이터베이스에 존재하지 않습니다.");
}
}
}
public class EquipmentManager : MonoBehaviour
{
void Start()
{
// 1. 퍼사드 객체 생성
EquipmentFacade facade = new EquipmentFacade();
// 2. DB 값 insert
facade.InsertEquipmentData();
// 3. 퍼사드로 데이터베이스 & 캐싱 & 메시징 로직을 한번에 조회
string name = "Staff";
facade.QueryAndDisplayEquipmentData(name);
}
}
퍼사드 패턴을 적용하니 메인 로직이 엄청 심플해졌다. 이처럼 퍼사드의 핵심은 인터페이스(API)를 적게 구성하는 것이다. 라이브러리에서 제공하는 클래스나 메소드가 많이 보이면, 프로그래머는 무엇을 사용하면 좋을지 망설이게 되고 호출하는 순서도 문서를 살펴보며 주의해야 하며 틀리기 쉽다. 따라서 퍼사드의 메서드를 가능하면 적게 구성하는 것이 좋다.
오해하기 말아야할 점은 퍼사드는 하위 시스템 클래스들을 캡슐화하는 것이 아니다. 그냥 서브 시스템들을 사용할 간단한 인터페이스를 제공할 뿐이다. 사용자가 서브 시스템 내부의 클래스를 직접 사용하는 것을 제한할 수는 없다. 그래서 캡슐화 보다는 오히려 추상화에 가깝다고 할 수 있다.