라이브러리, 프레임워크 또는 다른 복잡한 클래스들의 집합에 대해
단순화된 인터페이스를 제공하는 구조 패턴
복잡한 라이브러리나 프레임워크에 속하는 광범위한 객체의 집합들을 다뤄야 하는 상황
결과적으로 클래스들의 비즈니스 로직이 써드 파티 클래스들의 세부 구현에 강하게 결합돼 이해하고 유지보수하기 힘들어짐
퍼사드 - 복잡한 하위 시스템에 대한 간단한 인터페이스를 제공하는 클래스
퍼사드는 프로그램을 많은 기능을 가진 복잡한 라이브러리와 통합해야 하지만 일부 기능만 필요로 할 때 유용
1. 퍼사드는 하위 시스템의 기능의 특정 부분에 편리한 접근을 제공
- 클라이언트의 요청을 어디로 보내야 하는지, 각 부분을 어떻게 작동해야 하는지 알고 있음
2. 추가 퍼사드 - 단일 퍼사드를 무관한 기능으로 오염시켜 또 다른 복잡한 구조가 되는 걸 방지
- 클라이언트와 다른 퍼사드 모두가 사용 가능
3. 복잡한 하위 시스템 - 여러 다양한 객체로 구성됨
- 모든 객체가 의미 있는 작업을 하도록 하기 위해 객체를 올바른 순서로 초기화하는 것,
적절한 포맷의 데이터를 공급해주는 것 등 하위 시스템의 세부 구현을 파고들어야 함
- 하위 시스템 객체들은 퍼사드의 존재를 모르고, 시스템 내부에서 직접적으로 함께 동작함
4. 클라이언트 - 하위 시스템 객체들을 직접 호출하지 않고 퍼사드 사용
- 하위 시스템들은 시간이 지남에 따라 더 복잡해지고,
디자인 패턴을 적용하는 것도 대부분 더 많은 클래스를 생성하게 됨
- 하위 시스템은 더 유연해지고 다양한 맥락에서 재사용하기 더 쉬워지지만,
클라이언트로부터 요구하는 설정과 boilerplate 코드가 더 많아지게 됨
- 퍼사드는 클라이언트의 대부분의 요구를 충족하며
하위 시스템에서 가장 많이 사용되는 기능들로 향하는 지름길을 제공해 문제 해결
- 퍼사드를 만들어 하위 시스템의 각 계층에 대한 진입점을 정의하고,
하위 시스템들로 하여금 퍼사드를 통해서만 소통하게 해서 하위 시스템 간의 결합도를 낮출 수 있음
1. 기존 하위 시스템이 이미 제공하는 것보다 더 간단한 인터페이스를 만들 수 있는 지 확인
- 해당 인터페이스가 클라이언트 코드를 하위 시스템의 여러 클래스로부터 독립시킬 수 있으면 됨
2. 새로운 퍼사드 클래스에 해당 인터페이스를 선언하고 구현함
- 퍼사드는 클라이언트 코드의 요청을 하위 시스템의 적절한 객체로 redirect해야 함
- 퍼사드는 하위 시스템을 초기화하고 생명 주기를 관리할 책임이 있음 (클라이언트 코드가 이미 하고 있지 않다면)
3. 퍼사드 패턴의 장점을 최대한 활용하려면, 모든 클라이언트 코드가 파사드를 통해서만 하위 시스템과 소통하게 해야 함
→ 클라이언트 코드가 하위 시스템 코드의 변경으로부터 보호됨
4. 퍼사드가 너무 커진다면, 행위 일부분을 추출해 새로운 정제된 퍼사드 클래스 생성 고려
- 코드를 하위 시스템의 복잡성으로부터 고립시킬 수 있음
- 퍼사드가 프로그램의 모든 클래스에 결합된 ‘전지전능한 객체 (God object)’가 될 수 있음
- 퍼사드 - 기존 객체에 새로운 인터페이스 정의, 객체들의 전체적인 하위시스템과 동작,
어댑터 - 기존 인터페이스를 사용 가능하게 만듦, 대개 한 객체만 래핑함
- 추상 팩토리 클래스 - 하위 시스템 객체들이 클라이언트 코드에서 생성되는 방식만을 감추고 싶은 경우
퍼사드 패턴의 대안
- 플라이웨이트 패턴 - 작은 객체를 여러 개 만드는 방법,
퍼사드 패턴 - 하위 시스템 전체를 대표하는 하나의 객체를 만드는 방법
- 퍼사드 패턴 & 중재자 패턴 - 비슷한 역할, 밀접하게 결합된 클래스들의 협업을 체계화함
퍼사드 - 하위 시스템 객체들에 대한 단순화된 인터페이스를 정의하지만 새로운 기능을 도입하지 않음,
하위 시스템은 퍼사드의 존재를 모름, 하위 시스템 내부 객체들끼리는 직접 소통 가능
중재자 - 시스템의 컴포넌트들 간의 소통을 중앙화함,
컴포넌트들은 서로 직접 소통하지 않고 중재자 객체에 대해서만 알고 있음
- 퍼사드 패턴은 대부분 하나의 퍼사드 객체면 충분하기 때문에 싱글턴 패턴으로 변경 가능
- 퍼사드와 프록시는 둘 다 복잡한 객체를 완화하고 자체적으로 초기화한다는 점에서 비슷하지만,
프록시는 퍼사드와 달리 서비스 객체와 같은 인터페이스를 가지기 때문에 상호 교환 가능
/**
* The Facade class provides a simple interface to the complex logic of one or
* several subsystems. The Facade delegates the client requests to the
* appropriate objects within the subsystem. The Facade is also responsible for
* managing their lifecycle. All of this shields the client from the undesired
* complexity of the subsystem.
*/
class Facade {
protected subsystem1: Subsystem1;
protected subsystem2: Subsystem2;
/**
* Depending on your application's needs, you can provide the Facade with
* existing subsystem objects or force the Facade to create them on its own.
*/
constructor(subsystem1?: Subsystem1, subsystem2?: Subsystem2) {
this.subsystem1 = subsystem1 || new Subsystem1();
this.subsystem2 = subsystem2 || new Subsystem2();
}
/**
* The Facade's methods are convenient shortcuts to the sophisticated
* functionality of the subsystems. However, clients get only to a fraction
* of a subsystem's capabilities.
*/
public operation(): string {
let result = 'Facade initializes subsystems:\n';
result += this.subsystem1.operation1();
result += this.subsystem2.operation1();
result += 'Facade orders subsystems to perform the action:\n';
result += this.subsystem1.operationN();
result += this.subsystem2.operationZ();
return result;
}
}
/**
* The Subsystem can accept requests either from the facade or client directly.
* In any case, to the Subsystem, the Facade is yet another client, and it's not
* a part of the Subsystem.
*/
class Subsystem1 {
public operation1(): string {
return 'Subsystem1: Ready!\n';
}
// ...
public operationN(): string {
return 'Subsystem1: Go!\n';
}
}
/**
* Some facades can work with multiple subsystems at the same time.
*/
class Subsystem2 {
public operation1(): string {
return 'Subsystem2: Get ready!\n';
}
// ...
public operationZ(): string {
return 'Subsystem2: Fire!';
}
}
/**
* The client code works with complex subsystems through a simple interface
* provided by the Facade. When a facade manages the lifecycle of the subsystem,
* the client might not even know about the existence of the subsystem. This
* approach lets you keep the complexity under control.
*/
function clientCode(facade: Facade) {
// ...
console.log(facade.operation());
// ...
}
/**
* The client code may have some of the subsystem's objects already created. In
* this case, it might be worthwhile to initialize the Facade with these objects
* instead of letting the Facade create new instances.
*/
const subsystem1 = new Subsystem1();
const subsystem2 = new Subsystem2();
const facade = new Facade(subsystem1, subsystem2);
clientCode(facade);
// Output.txt
Facade initializes subsystems:
Subsystem1: Ready!
Subsystem2: Get ready!
Facade orders subsystems to perform the action:
Subsystem1: Go!
Subsystem2: Fire!
참고 자료: Refactoring.guru