[Design Pattern] 퍼사드 패턴

olwooz·2023년 2월 19일
0

Design Pattern

목록 보기
11/22
라이브러리, 프레임워크 또는 다른 복잡한 클래스들의 집합에 대해 
단순화된 인터페이스를 제공하는 구조 패턴

문제

복잡한 라이브러리나 프레임워크에 속하는 광범위한 객체의 집합들을 다뤄야 하는 상황

  • 일반적으로 모든 객체들을 초기화하고, 의존성을 추적하고, 메서드들을 올바른 순서대로 실행하는 등등의 작업 필요

결과적으로 클래스들의 비즈니스 로직이 써드 파티 클래스들의 세부 구현에 강하게 결합돼 이해하고 유지보수하기 힘들어짐

해결책

퍼사드 - 복잡한 하위 시스템에 대한 간단한 인터페이스를 제공하는 클래스

  • 하위 시스템을 직접적으로 다루는 것에 비해 제한된 기능을 제공하지만, 클라이언트가 진정 필요로 하는 기능만 포함

퍼사드는 프로그램을 많은 기능을 가진 복잡한 라이브러리와 통합해야 하지만 일부 기능만 필요로 할 때 유용

구조

1. 퍼사드는 하위 시스템의 기능의 특정 부분에 편리한 접근을 제공
   - 클라이언트의 요청을 어디로 보내야 하는지, 각 부분을 어떻게 작동해야 하는지 알고 있음
    
2. 추가 퍼사드 - 단일 퍼사드를 무관한 기능으로 오염시켜 또 다른 복잡한 구조가 되는 걸 방지
   - 클라이언트와 다른 퍼사드 모두가 사용 가능
    
3. 복잡한 하위 시스템 - 여러 다양한 객체로 구성됨
   - 모든 객체가 의미 있는 작업을 하도록 하기 위해 객체를 올바른 순서로 초기화하는 것, 
     적절한 포맷의 데이터를 공급해주는 것 등 하위 시스템의 세부 구현을 파고들어야 함
   - 하위 시스템 객체들은 퍼사드의 존재를 모르고, 시스템 내부에서 직접적으로 함께 동작함
   
4. 클라이언트 - 하위 시스템 객체들을 직접 호출하지 않고 퍼사드 사용

적용

복잡한 하위 시스템에 제한적이지만 간단한 인터페이스가 필요한 경우

- 하위 시스템들은 시간이 지남에 따라 더 복잡해지고, 
  디자인 패턴을 적용하는 것도 대부분 더 많은 클래스를 생성하게 됨
  
- 하위 시스템은 더 유연해지고 다양한 맥락에서 재사용하기 더 쉬워지지만, 
  클라이언트로부터 요구하는 설정과 boilerplate 코드가 더 많아지게 됨
  
- 퍼사드는 클라이언트의 대부분의 요구를 충족하며 
  하위 시스템에서 가장 많이 사용되는 기능들로 향하는 지름길을 제공해 문제 해결

하위 시스템을 계층 구조로 만들고 싶은 경우

- 퍼사드를 만들어 하위 시스템의 각 계층에 대한 진입점을 정의하고, 
  하위 시스템들로 하여금 퍼사드를 통해서만 소통하게 해서 하위 시스템 간의 결합도를 낮출 수 있음

구현방법

1. 기존 하위 시스템이 이미 제공하는 것보다 더 간단한 인터페이스를 만들 수 있는 지 확인
   - 해당 인터페이스가 클라이언트 코드를 하위 시스템의 여러 클래스로부터 독립시킬 수 있으면 됨
    
2. 새로운 퍼사드 클래스에 해당 인터페이스를 선언하고 구현함
   - 퍼사드는 클라이언트 코드의 요청을 하위 시스템의 적절한 객체로 redirect해야 함
   - 퍼사드는 하위 시스템을 초기화하고 생명 주기를 관리할 책임이 있음 (클라이언트 코드가 이미 하고 있지 않다면)
   
3. 퍼사드 패턴의 장점을 최대한 활용하려면, 모든 클라이언트 코드가 파사드를 통해서만 하위 시스템과 소통하게 해야 함
   → 클라이언트 코드가 하위 시스템 코드의 변경으로부터 보호됨
    
4. 퍼사드가 너무 커진다면, 행위 일부분을 추출해 새로운 정제된 퍼사드 클래스 생성 고려

장단점

장점

- 코드를 하위 시스템의 복잡성으로부터 고립시킬 수 있음

단점

- 퍼사드가 프로그램의 모든 클래스에 결합된 ‘전지전능한 객체 (God object)’가 될 수 있음

다른 패턴과의 관계

- 퍼사드 - 기존 객체에 새로운 인터페이스 정의, 객체들의 전체적인 하위시스템과 동작, 
  어댑터 - 기존 인터페이스를 사용 가능하게 만듦, 대개 한 객체만 래핑함
  
- 추상 팩토리 클래스 - 하위 시스템 객체들이 클라이언트 코드에서 생성되는 방식만을 감추고 싶은 경우 
                    퍼사드 패턴의 대안

- 플라이웨이트 패턴 - 작은 객체를 여러 개 만드는 방법, 
  퍼사드 패턴 - 하위 시스템 전체를 대표하는 하나의 객체를 만드는 방법
  
- 퍼사드 패턴 & 중재자 패턴 - 비슷한 역할, 밀접하게 결합된 클래스들의 협업을 체계화함
  퍼사드 - 하위 시스템 객체들에 대한 단순화된 인터페이스를 정의하지만 새로운 기능을 도입하지 않음, 
          하위 시스템은 퍼사드의 존재를 모름, 하위 시스템 내부 객체들끼리는 직접 소통 가능
  중재자 - 시스템의 컴포넌트들 간의 소통을 중앙화함, 
          컴포넌트들은 서로 직접 소통하지 않고 중재자 객체에 대해서만 알고 있음
    
- 퍼사드 패턴은 대부분 하나의 퍼사드 객체면 충분하기 때문에 싱글턴 패턴으로 변경 가능

- 퍼사드와 프록시는 둘 다 복잡한 객체를 완화하고 자체적으로 초기화한다는 점에서 비슷하지만, 
  프록시는 퍼사드와 달리 서비스 객체와 같은 인터페이스를 가지기 때문에 상호 교환 가능

TypeScript 예제

/**
 * 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

0개의 댓글