-
퍼사드 패턴에 대한 고찰기타 2025. 4. 5. 00:46
안녕하세요, Mash-Up 안드로이드 팀 멤버 이창환입니다.🐗
객체지향에 관심이 많은 개발자로써 객체지향의 꽃이라 부를수 있는 디자인 패턴들 중 아주 간단하지만 위력적일 수 있는 퍼사드 패턴에대해 알아보는 시간을 가지려 합니다.
디자인 패턴이란?
프로그램 개발에서 자주 나타나는 과제를 해결하기 위한 방법 중 하나로, 과거의 소프트웨어 개발 과정에서 발견된 설계의 노하우를 축적하여 이름을 붙여, 이후에 재이용하기 좋은 형태로 특정의 규약을 묶어서 정리한 것이다.
출처: wikipidia쉽게 말하자면 선배개발자가 남겨놓은 게임공략집 이라고 생각하시면 됩니다.
우리는 프로그램을 개발하며 많은 문제들에 빠지게 됩니다.
이러한 부분들은 우리의 천재적인 선배 개발자 또한 마찬가지였을테고 그분들이 오랜시간의 경험과 고민을 녹여놓은 산물입니다.디자인 패턴은 문제를 어떤 시각으로 바라볼 것인지 또한 그안에서 각 객체들의 역할과 책임을 어떤식으로 분배할건지를 제시해줍니다.
이러한 디자인 패턴을 학습하며 선배 개발자 분들의 번뜩이는 아이디어와 시야를 공유할 수 있게됩니다.
유명한 디자인 패턴으로는 GOF 패턴이 있으며 돌아보면 항상 우리 주변에서 함께하고 있었을것입니다.
팩토리 패턴,빌더 패턴,싱글턴 패턴,어댑터 패턴, 옵저버 패턴, 전략 패턴 등 이름만 들어도 친숙하지 않나요?
퍼사드 패턴이란?
문제 상황
[ 한줄요약 ]
복잡도가 높은 객체 시스템을 단순화시켜 사용자에게 제공, 사용자는 내부 시스템을 블랙박스인 상태로도 사용할 수 있게 만든다.
개발을 진행하다보면 복잡도가 높은 라이브러리를 다루고 도입하는 경우, 도메인이 굉장히 복잡하여 도메인 룰을 표현하는 객체간의 상호작용을 학습하는데 비용이 많이 드는 경우가 종종 있습니다.
이러한 복잡한 시스템을 개발에 참여하는 모든 팀원들이 화이트박스로 알고 있기에 너무 큰 비용이 발생하지만 모든 팀원이 해당 기능을 사용은 해야할때 사용을 고려해볼 법한 패턴입니다.(해당 시스템을 사용하는 리소스를 줄일 수 있음)
해결 방안
퍼사드 객체를 사용자에게 제공, 내부 시스템의 복잡도는 해당 객체 내부로 캡슐화 시키고(복잡한 시스템의 사용법을 객체내 로직으로 작성) 해당 퍼사드를 사용하는 사용자에게는 간단한 인터페이스를 제공, 내부의 복잡도 높은 도메인 지식을 익힐 필요 없이 기능을 사용할 수 있게 합니다.
구현예시
멧돼지가 밥먹는 행위를 코드로 표현하라고 하면 생각보다 굉장히 복잡할 수 있습니다.(순서가 뒤바뀌면 안되므로)
이러한 상황을 예시로 표현해봤습니다.
복잡도가 높은 객체들의 시스템
/ 멧돼지 서브시스템 (밥먹고 똥싸는 기능) // 입 class Mouth { fun chew(food: String): String { println("Mouth: Chewing $food") return "chewed_$food" } } // 위장 class Stomach { fun digest(food: String): String { println("Stomach: Digesting $food") return "digested_$food" } } // 소장 class SmallIntestine { fun absorbNutrients(food: String): String { println("Small Intestine: Absorbing nutrients from $food") return "nutrient_essence" } } // 대장 class LargeIntestine { fun excreteWaste(food: String): String { println("Large Intestine: Excreting waste from $food") return "waste" } }
멧돼지 시스템을 간단히 사용할 수 있도록 제공하는 퍼사드
class WildBoarFacade { // 의존성 주입으로 받지않고 하드코딩 GOF를 따름 private val mouth = Mouth() private val stomach = Stomach() private val smallIntestine = SmallIntestine() private val largeIntestine = LargeIntestine() fun takeMeal(food: String): String { // 복잡도 높은 부분 val chewedFood = mouth.chew(food) val digestedFood = stomach.digest(chewedFood) val enzymeMixedFood = duodenum.mixWithEnzymes(digestedFood) val nutrientEssence = smallIntestine.absorbNutrients(enzymeMixedFood) return largeIntestine.excreteWaste(enzymeMixedFood) } }
위의 예시를 살펴보면 객체들이 일을 처리하는 순서(복잡도 높은 도메인 지식)가 퍼사드 객체 내부에 갈무리되어 사용자는 해당 상황을 몰라도 멧돼지에게 밥을 먹일수 있게됩니다.
필자의 퍼사드 패턴에 대한 생각
해당 풀이는 개인적인 요소가 다분합니다.
비판적인 시각으로 바라보고 함께 고민해주시면 좋을것 같습니다.
패턴이름 alias(기억에 남는 별칭이 필요합니다.)
우선적으로 퍼사드라는 이름이 입에 착착 안붙지 않습니다.(저만 그럴수도 있지만)
패턴은 그 패턴을 사용했다는것 만으로도 작업자의 의도를 드러낼 수 있고 또한 별다른 설명없이 다른 개발자와 소통이 가능하다는 점에서 강력한 요소로 작용합니다.
이에 이름을 꼭 기억하고 관련 클래스명에 명시해줘야한다고 생각합니다.그래서 퍼사드 패턴을 어떻게 와닿게 기억해볼까 고심하고 또 고심하였습니다.
퍼사드의 뜻은 다음과 같습니다.
파사드(Façade)는 ‘얼굴’을 의미하고 건축에서는 건물의 얼굴, 즉, 주요 외부 전면을 나타냅니다
여기서 딱 떠오른 단어 "얼굴마담"
패턴의 의도 및 동작을 봐도 퍼사드 밑에 복잡한 것들을 숨기고(객체보다 큰단위인 서브시스템 단위로) 간단한 인터페이스를 제공하는 것입니다.
즉 못생긴 애들은 밑에서 조용히 일하고 클라이언트단에 얼굴마담을 세워서 클라이언트는 행복하게 만들어주는것과 같은 슬픈 현실을 반영하고 있습니다. (차은우/카리나가 서빙하고 필자가 열심히 접시닦고 요리하는것과 같음)그래서 쉬운 용어로 얼굴마담 패턴이라는 별칭으로 기억하면 어떨까 제시해봅니다.
패턴의 이해를 위한 생활속의 예시
이 패턴을 간단하게 설명하기 위해서 IOT 자동화를 들고싶습니다.
필자는 IOT가 유행할때 "시리야 술마실꺼야"라고 핸드폰한테 외치면 조명 다 꺼지고 특정조명만 25%로 켜지면서 블루투스 스피커로 재즈가 나오는 간지나는 스마트홈을 조성해본 경험이있습니다.이런 자동화를 하려면 아주 많은 요소들이 복합적으로 작용합니다.
전구1~7, 식탁조명, TV, 블루투스 스피커 이런것들이 복합적으로 상호작용해야 우아하게 시리한테 명령 하는것이 가능합니다.(조명 각각이 프로그래밍 세계에서는 객체 -> 심지어 가상세계에서는 호출순서도 영향을 줄 수 있다.)
물론 이런 것들을 사용자가 하나하나 직접 실행할 수 있습니다.
전구 1~7번 끄고, 식탁등 25%로 키고, TV끄고 등 근데 이러면 귀찮기도 하고 순서가 있다면 더 귀찮고 복잡해지고 나중에는 순서 같은건 기억조차 안나게 될겁니다.
심지어 이걸 엄마(다른 개발자)한테 켜달라고 부탁하는것을 가정해 봅시다.임무를 완수 못한채 엄마가 화나있을 가능성이 큽니다.
이를 이제 자동화라는 툴(퍼사드)로 묶어서 한꺼번에 순서까지 조정해서 미리 설정해놓으면 그냥 "시리야 술마실꺼야" 하면 간지나는 스마트 홈으로 탈바꿈합니다.
이것이 필자가 생각하는 퍼사드의 예시라 생각합니다.
패턴에 대한 결론[ 한줄요약 ]
복잡한 객체의 상호작용을 미리 정의해놓은 객체에 명시하여 사용자는 편리하게 사용하고 각 서브시스템과의 의존성을 낮출 수 있다.
여기까지가 줄 글로 든 예시였으며 이에 대한 포인트를 각각 간결하게 살펴보려 합니다.
- 서브시스템 단위의 객체지향이라 생각합니다.
- 큰 설명없이 이해할 수 있을것 입니다. 물론 모든 은닉화 이런측면에서 명확히 떨어지지 않지만 대략적인 결이 비슷 합니다.
- 퍼사드가 존재한다고 객체자체를 못 다루는 은닉화가 아닙니다.
- 필요에 의해서(좀 더 많은 기능을 사용)로우레벨의 객체를 직접적으로 클라이언트가 다룰 수 있습니다.
- IOT에서 자동화를 만들어놨어도 전구를 각각 켜고 끌수 있는것과 같습니다.(용도에 맞춰서 사용)
- 퍼사드는 여러개가 존재할 수 있습니다.
- 퍼사드는 여러개를 만들수 있으며 필요에의해서 서브퍼사드를 만들 필요가 있습니다.
- IOT에서 자동화를 꼭 하나만 만들어서 쓸필요는 없지 않은것과 같은 이야기입니다.
- 스마트홈에서 간지조명 기능만 가지면 안됩니다. 영화보는 구성 켜기 등 많은 자동화가 생성될것입니다.
- 퍼사드는 외부로부터 의존성을 주입받아서 유연하게 만들필요는 없다고 생각합니다.(개인적인 의견)
- 이 부분은 자료마다 의견이 달라서 밑에서 다시 다뤄보려합니다.
- 서브시스템(관련객체들이 모인 단위)와 소통하는 인터페이스를 제공 -> 계층화가 쉽습니다.
자료마다 다른 내용에 대한 필자의 의견정리
위쪽에 "퍼사드는 외부로부터 의존성을 주입받아서 유연하게 만들필요는 없다고 생각합니다.(개인적인 의견)" 이부분이 자료마다 조금씩 달라서 필자의 의견을 정리하였습니다.
구현예제를 설명하는 부분에서 (262p 일부 발췌)
이 구현은 생성한 코드 생성자의 종류를 하드코딩했기 떄문에 프로그래머가 어떤 아키텍쳐를 목표로 하는지는 명시하지 않아도 됩니다. 이는 하나의 목표 아키텍처를 갖는다면 의미가 있지만 여러 아키텍처를 대상으로 한다면 Compiler 클래스의 생성자를 변경해야 합니다.즉 CodeGenerator를 매개변수로 받게끔 변경해야합니다.프로그래머는 compiler를 인스턴스화할 때 사용할 생성기(generator)가 무엇인지 알려줍니다. compiler는 다른 매개변수로 정의할 수 있는데 Scanner와 ProgramNodeBuilder를 매개변수로 정의하면 응용성이 늘어나게 됩니다. 그러나 이는 퍼사드 패턴의 임무를 없애는 것입니다. 퍼사드 패턴의 임무는 일반적인 사용을 위해 인터페이스를 간소화하는 것이기 떄문입니다.
이 부분을 풀어보자면 생성자로 내부에서 사용할 객체를 받아서 처리하는것이 코드 변동성에 대해 유연성은 올라가겠지만 이는 곧 서브 시스템의 클래스 관계도를를 프로그래머가 알고있으며 인스턴스 생성 및 사용에 관여하는것 자체가 이미 서브 시스템을 화이트 박스로 알고있다는 전제가 깔렸다는것과 같다고 생각합니다.(객체를 그냥 사용하는것과 다를바가 없어짐)
즉 쉽게 사용하려고 기껏 만들어놓은 퍼사드가 필요없어지는 상황이 벌어지는것 입니다.
구현예제를 설명하는 부분에서 (297p)
퍼사드 객체를 생성할때 관련된 서브시스템의 객체를 전부 생성자를 통해 주입받아 사용하는 형태
-> 즉 Gof 패턴에서 말하는 부분과 반대 (유연성 높이되 인터페이스의 편리함이 낮아짐)필자는 Gof, 헤드퍼스트 두가지 관점에서 바라본다면 GOF 패턴이 맞다고 봅니다.
IOT를 예시로 봤을때 편의를 위해 자동화를 만들어놨는데 이 자동화에서 몇번 전구가 어떻게 움직이는지 직접 관여하고 알아야하는것 부터가 비용이며 퍼사드의 의미를 잃는다고 생각합니다.그래서 이러한 인스턴스를 관리하는 룰 자체를 퍼사드 객체에서 들고있어야한다고 생각합니다.
만약 유연성을 추구한다면 퍼사드 객체 자체를 추상화하여 쉽게 갈아끼우는 정도가 적당하다고 생각합니다.
사실 유연성이라는 단어 자체가 너무 마성의 단어라 그냥 무조건 좋아보이는 현상이 발생하지만
이런 경우를 보면 유연성이 만능은 아니라는 생각이듭니다.
개인적으로 어댑터 패턴과 굉장히 유사하다는 인상을 최초에 강하게 받았습니다. 하지만 어댑터 패턴과는 어느 정도의 차이가 있기도 하고 사실상 패턴은 그 이름에 의미가 있듯 사용용도의 차이라 생각합니다.
- 어댑터: 이미 정해져 있는 인터페이스에 외부코드를 밀어넣기위한 방법 (끼워넣기가 목표)
- 퍼사드: 서브 시스템 클래스들을 종합하는 얼굴마담을 앞에 세우는 방법 (단순화가 목표)
사실 패턴에서 하는 행위가 추상화랑 클래스 분리 이런거라 비슷해보이는게 많지만 결국 이름이 가지는 힘에서 다른 부분이 분명 존재한다고 생각합니다.
이 부분에 뭔가 부족하다면 헤드퍼스트 디자인 패턴책에 두 패턴을 비교해놓은 부분이 있습니다.
굉장히 쉽게 설명 되어있어 이것을 참고해보면 좋을것 같습니다.퍼사드 패턴 사용시 이점
- 서브시스템의 구성요소를 보호할 수 있습니다.
이를 통해 사용자가 다룰 객체가 줄어들며 이는 쉬운 사용으로 이어집니다. - 서브시스템과 사용자 코드간의 결합도를 약하게 만듭니다.
- 퍼사드를 사용하는게 필수는 아닙니다. 사용자에게 선택권을 줄 수 있습니다.
- 계층화가 쉽고 계층간의 결합도를 낮출 수 있습니다.
- 굉장히 복잡한 객체들을 구성해서 사용해야하는 상황(누군가에게 설명하기도 힘든 상황)
- 다른 라이브러리나 다른 객체들의 상호작용을 한번더 단순화 시킬때(우리 시스템에 맞춰서 단순화 시키기)
- 뭔가 객체나 함수의 호출순서가 있어야하는 상황(물론 잘못 짰을 수 있지만 잘못 짰어도 사용해야할때가 있으니 보정방법으로) 퍼사드객체를 사용(퍼사드는 꼭 여러객체의 상호작용에만 적용하는것이 아닌 복잡한 단일 객체에도 적용가능)
- guru 사이트
- Gof의 디자인 패턴 254p~264p
- 헤드퍼스트 디자인 패턴 290p~306p
'기타' 카테고리의 다른 글
매쉬업 안드로이드 블로그 만들기와 그 후기 (0) 2023.03.06 코딩테스트 플랫폼 정리 (0) 2023.02.14 - 서브시스템 단위의 객체지향이라 생각합니다.