Facade

최완식·2022년 9월 20일
0

Design Patterns

목록 보기
14/26
post-thumbnail

GoF의 디자인 패턴, 파사드 패턴에 대해 알아본다.

해당 글은, 다음의 코드를 기반으로 이해하는 것이 편리합니다.

핵심 요약

  • 어떤 기능을 처리하기 위해 여러 객체들 사이의 복잡한 메서드 사용을 감추는 패턴
  • 즉, 사용하는 쪽에서는 여러 클래스를 알필요 없이 단순하게 사용할 수 있다.
  • 블랙박스화 라고 생각해도 좋다.
  • 라이브러리를 제공할 시 사용하면 좋다.
  • 물론 사용하는 방법을 제한해서 제공하기 때문에, 어찌보면 사용하는 쪽에서 유연성이 떨어진다고 생각할 수도 있겠다.
    • 다만 보통은 서브 시스템 클래스 접근도 열어주기 때문에 그렇게 생각은 잘 하지 않는 편.

예시

  • Class Diagram이 어렵다면 여기를 참고하자.
  • 해당 코드는 DB에서 특정 이름을 기반으로 자료를 조사하는 동작을 모사한 것이다.
  • 이 때, 캐시를 사용하여 속도를 높히려 한다.
  • 이 과정에서 캐시 조회, DB 조회, 캐시 삽입등 어떠한 로직이 필요하다.
  • 만약 이 과정을 사용하는 측에서 조합하도록 한다면 아래와 같은 더러운 코드가 발생한다.
// Not using Facade

internal func main() {
    let dbms = DBMS()
    let cache = Cache()

    let name = "wansik"


    if let row = cache.get(with: name) {

        let message = Message(row: row)
        print(message.makeName())
        print(message.makeBirthday())
        print(message.makeEmail())

    } else if let row = dbms.query(name: name) {
        cache.put(row: row)

        let message = Message(row: row)
        print(message.makeName())
        print(message.makeBirthday())
        print(message.makeEmail())

    } else {
        print("없는 이름입니다..")
        return
    }
}
  • 어떻게 사용해야 하는지에 대해 추가로 조사해야 하기 때문에 사용성 측면에서 좋지 못하다.
  • 개발자가 하나의 클래스를 만들고, 그 안에서 어떻게 동작할지에 정의해두면 편하지 않을까?

// Using Facade
internal struct Facade {

    internal func run(name: String) {
        if let row = cache.get(with: name) {

            let message = Message(row: row)
            print(message.makeName())
            print(message.makeBirthday())
            print(message.makeEmail())

        } else if let row = dbms.query(name: name) {
            cache.put(row: row)

            let message = Message(row: row)
            print(message.makeName())
            print(message.makeBirthday())
            print(message.makeEmail())

        } else {
            print("없는 이름입니다..")
            return
        }
    }

    private var dbms = DBMS()
    private var cache = Cache()
}

internal func main() {
    let facade = Facade()

    facade.run(name: "wansik")
}
  • Facade 객체 안에 해당 함수의 내용을 이동했다.
  • 외부에서는 run(name:)만 호출하면 된다.

동기

  • 컴파일러 시스템을 생각해보자.
  • Parser, Scanner 등의 여러 작업을 거쳐야 비로소 바이너리가 만들어진다.
  • 하지만 사용하는 쪽에서는 compile() 메서드 하나만 알면된다.

활용성

  • 단순한 인터페이스 제공이 필요할 때
  • 재사용을 위한 작은 클래스를 만들었으나, 실제 사용하는 것이 어려워졌을 때
  • 서브시스템 간의 결합도를 줄이고 싶을 때
  • 서브시스템을 계층화 시키고 싶을 때 묶어서 접근점을 제공하여 종속성을 줄일 수 있음

구조

참여자

  • Facade(Facade)
    • 단순하고 일관된 통합 인터페이스 제공
    • 서브 시스템을 구성하는 클래스가 어떤 클래스를 요청해야 하는지 알고 있음
      • 위의 예시로부터 생각해보면 원래는 main에서 개발자가 하다가 옮긴 것 뿐임
      • 즉, 만든 개발자가 어떻게 동작하는지 다 알고 코드를 옮겨둔 것
    • 사용자의 요청을 서브시스템 객체들에게 전달함
  • Subsystem(DBMS, Cache)
    • 서브시스템의 기능을 구현함
    • 작업을 실제로 처리하지만 Facade에 대한 아무런 정보가 없음

협력 방법

  • 사용자는 Facade에 정의된 인터페이스를 사용하여 상호작용함
  • Facade는 알맞은 객체에게 해당 작업을 전달함
  • 사용자는 서브시스템을 구성하는 객체로 직접 접근할 필요가 없음

결과

  1. 서브 시스템의 구성 요소를 보호할 수 있다.
  2. 서브 시스템과 사용자 코드간의 결합도를 약하게 만든다.
  3. 응용프로그램 쪽에서 서브 시스템 클래스를 사용하는 것을 완전히 막지는 않는다.

관련 패턴과 차이점

  • 추상 팩토리(Abstract Factory)
    • 서브 시스템에 독립적인 방법으로 객체 생성 인터페이스를 제공하기 위해 함께 사용 가능
  • 중재자(Mediator) - 기존에 존재하는 클래스의 기능성을 추상화하는 면에서 유사, 하지만
    • 중재자: 객체들간의 협력 관계를 추상화하여 기능의 집중을 막자.
    • 퍼사드: 서브시스템 인터페이스 자체를 추상화하여 사용을 용이하게 하자.
  • 단일체(Singleton) - 하나만 있어도 되면 싱글톤으로 구현

생각해볼 점

  • UIKit과 같은 라이브러리가 이런식으로 많이 되어있다.
  • 사용하기 편하게 하기 위해 UILabel 같은 것을 제공하지만,
  • 실질적으로 아랫단은 CoreText와 같은 것으로 되어있다.
  • 맞는 예가 아닐 수 있으나, 결국 사용성을 위해 Interface의 제약을 걸었다는 점에서 유사하다 할 수 있다.

Reference

profile
Goal, Plan, Execute.

0개의 댓글