퍼사드 패턴(Facade Pattern) ; 느슨한 결합(Loose Coupling)
Facade : 겉모양, 외관
어떤 서브 시스템의 일련의 인터페이스에 대한 통합된 인터페이스를 제공한다.
퍼사드에서 고수준 인터페이스를 정의하기 때문에 서브 시스템을 더 쉽게 사용할 수 있다.
한마디로, 서브 시스템의 복잡한 동작을 미리 정의하는 고수준의 인터페이스를 정하는 것이다.
책에서 나오는 예제 - 홈시어터의 복잡한 on/off 단계를 Facade 로 해결
책에서 나오는 예제는 홈시어터를 통해 영화를 보는 과정의 복잡성을 먼저 소개하고,
이를 퍼사드 패턴을 통해 한 단계로 축약하는 극단적인 케이스를 소개한다.
홈시어터로 영화를 보는 단계에 대해 먼저 알아보자.
- 팝콘 튀기기
- 전등 조절
- 스크린을 내린다
- 프로젝터 전원 on
- DVD 외부입력 설정
- 스크린 모드로 프로젝트 조절
- 앰프 조정 (on, DVD 출력, 서라운드 모드)
- DVD 재생
만약 이런 일련의 과정을 시스템으로 구현한다고 가정해보자. 벌써 등장하는 객체만 해도 (팝콘, 전등, 스크린…) 머리가 복잡해진다.
그리고 이런 순서는 각 객체가 제공하는 메소드를 순서대로 클라이언트 측에서 일일히 찾아서 사용해야 하는 번거로움이 있다.
따라서, 이를 한개의 인터페이스로 묶어버리고, 클라이언트에겐 해당 인터페이스만 제공하는 것으로 느슨한 결합을 달성 할 수 있다.
일종의 버튼 하나에 모든 기능을 축약한 리모콘의 기능을 떠올려 보면 된다.
최소 지식 원칙 ; 데메르트의 법칙
정말 친한 친구하고만 얘기해라
객체 사이의 상호작용은 될 수 있으면 아주 가까운 친구 사이에서만 허용하는 것이 좋다는 설계 원칙
최소 지식 원칙을 따를 수 있는 가이드라인 4가지(어떤 메소드에서든지 다음 4 종류의 객체의 메소드만을 호출해라)
- 객체 자체
- 메소드에 매개변수로 전달된 객체
- 그 메소드에서 생성하거나 인스턴스를 만든 객체
- 그 객체에 속하는 구성요소
장점
- 객체들 사이의 의존성을 줄일 수 있다.
- 소프트웨어의 관리가 용이해 진다.(작은 책임을 여러 객체가 나눠서 가지기 때문에)
단점
- 단순 메소드 호출을 처리하기 위한 래퍼 객체가 생긴다.
- 시스템의 복잡도가 높아진다.
- 객체 생성 / 소멸에 대한 자원낭비로 인해 성능이 떨어질 수 있다.
결론
클라이언트는 퍼사드가 만약 존재하지 않다면, 모든 객체와 연결되어 있어야 하므로 최소 지식 원칙에 위배된다.
결국 퍼사드를 통해 최소 지식 원칙을 고수하면, 결합도가 낮아지게 되고 퍼사드에선 각 작업방식에 대한 책임을 부여하게 되므로
기본적으로 관리가 용이하다는 장점이 있다.
하지만, 퍼사드 내부에서 사용하는 서브시스템 간 최소 지식 원칙을 고수하기 힘들 만큼 이미 복잡한 시스템이라면,
퍼사드를 추가하는 것을 다시 한번 고려해봐야 한다.
Spring 속 Facade Pattern
Spring 속 구현체에도 물론 Facade Pattern 이 적용된 부분이 있겠지만,
조금 더 친숙한 부분에서 Facade pattern 을 찾을 수 있었다. 바로 각 Controller의 End Point
가 바로 Facade 패턴이 적용된 부분이다.
예를 들어, 상품 주문에 대한 주문하기 API
를 갖고 있는 Controller 가 있다고 가정해보자.
내부적으론 OrderService, ShippingService, InventoryService 등 여러 서비스에 각 트랜잭션을 발생시켜야 할 것이다.
혹은, 하나의 서비스에서 한 트랜잭션으로 Order, Shipping, Inventory 등의 일련의 작업을 진행할 수도 있을 것이다.
결과적으론 프론트에서 API를 호출하면 주문이 된다는 것이다. 만약 이런 end-point 가 각각 나눠져 있다면?
주문 API, 배송 API, 재고변경 API 등등 각 엔드포인트를 클라이언트 측에서 각각 호출해야 할 것이다.
하지만, 일종의 퍼사드 패턴을 적용한 하나의 API를 제공하기 때문에 클라이언트에서는 해당 API가 어떤 역할을 하는 지만 보고,
규격에 맞춰서 요청을 보내면 된다.
생각해보자
퍼사드를 통해 일련의 작업방식을 고수준의 인터페이스 하나로 클라이언트에게 유연하게 제공할 수 있다는 것을 알았다.
스프링에선 이미 이런 패턴을 정말 적극적으로 활용하고 있었다는 것도 알게되었다.
문득 ‘일련의 작업방식을 하나의 메소드(인터페이스)로 묶는다는 점에서 트랜잭션과 그 의미가 비슷한게 아닌가?’ 라는 생각이 들었다.
트랜잭션과 퍼사드 패턴
일련의 작업을 하나의 메소드 내에서 일목요연하게 정리한다는 점에서 비슷해보 일 수 있으나, 적용되는 기술과 목적이 아예 다르다.
공통점
- 일련의 작업목록들을 하나의 메소드로 묶는다.
차이점
- 퍼사드 패턴 : 결국 클라이언트에서 사용할 인터페이스를 정의한다.
- 트랜잭션 : 작업단위로 그 목적은 데이터의 무결성을 위함이다.
객체지향생활체조 원칙과 데메르트의 법칙
객체지향생활체조 원칙 중 모든 원시값을 포장해라 라는 원칙이 나온다.
이를 통해 결합도를 낮추고 조금 더 객체지향적인 설계가 가능하다는 것이 목적이다.
이렇게 원시값을 포장하게 되면, 원시값에서 제공하는 많은 기능들을 제한하여 개발자가 필요로 하는 기능만 재정의 하여 제공하는 것이 가능하다.
위에서도 작성했듯이 관리에 대한 용이성과 성능상 효율성은 반작용이다.
실제 업무에선 이런 의견 사이에서 충돌이 일어날 수도 있다.
- 최소지식원칙에 의해 이 원시값은 별도 객체로 감싸야 합니다.
- 어차피 우리(개발팀)끼리는 의미 다 통하는데 뭘 그렇게 까지해, 그거 신경 쓸 시간 없어 그냥 그렇다 쳐
정말 장기적인 관점에서 객체지향적인 것을 고려하지 않고 개발자 간 편의성만 고려하면 되는걸까?…
한번 각자의 위치에서 고민해 볼 필요는 있다고 본다.