# Design Pattern ## 구조 패턴 - 객체가 구조를 이루는 패턴 - 둘 이상의 객체가 상호작용하며 더 큰 의미를 만들어내는 패턴 - Bridge, Decorator, Facade, Flyweight, Proxy, Composite, Adapter가 있음 ## 01. Decorator ### ✅ 설명 - 기능 확장 시 서브클래싱의 대안으로 제시 가능 - 클래스는 단일 상속만 가능하며, 형제관계인 클래스의 코드가 필요한 경우 부모클래스와 동시 상속받을 수 없음 → 각 기능도 필요한 상태에서 각 기능을 합친 기능도 필요한 경우에 Decorator Pattern 사용 가능 - 마트료시카처럼 자기자신을 자기자신으로 감쌈 → 클래스로 추상화하기 → class와 subclass를 쉽게 확장할 수 있다는 장점이 존재 ### ⚠️ 한계점 - Swift는 POP를 통해 기능의 수평확장을 지원하고 있음 - Decorator Pattern은 랩핑 과정이 필요하기 때문에 관리에 어려움이 있으며, 랩핑때문에 메모리 소비가 생김 ### 📎 예제 코드 ```swift class Calculator { private func setUp () { print("계산기 세팅") } func calculating() { setUp() } } class Plus: Calculator { override func calculating() { super.calculating() print("더하기") } } class Minus: Calculator { override func calculating() { super.calculating() print("빼기") } } class Multiply: Calculator { override func calculating() { super.calculating() print("곱하기") } } class Division: Calculator { override func calculating() { super.calculating() print("나누기") } } class PlusAndMinus: Plus { override func calculating() { super.calculating() print("빼기") } } let plusAndMinus = PlusAndMinus() plusAndMinus.calculating() // 계산기 세팅 // 더하기 // 빼기 ``` ![](https://hackmd.io/_uploads/BkCRFuF1a.png) ```swift class Calculator { private func setUp () { print("계산기 세팅") } func calculating() { setUp() } } class DecoratedCalculator: Calculator { var calculator: Calculator? init(_ calculator: Calculator? = nil) { self.calculator = calculator } override func calculating() { super.calculating() calculator?.setUp() calculator?.calculating() } } class Plus: DecoratedCalculator { override func calculating() { super.calculating() print("더하기") } } class Minus: DecoratedCalculator { override func calculating() { super.calculating() print("빼기") } } class Multiply: DecoratedCalculator { override func calculating() { super.calculating() print("곱하기") } } class Division: DecoratedCalculator { override func calculating() { super.calculating() print("나누기") } } let plusAndMinus = Minus(Plus()) plusAndMinus.calculating() // 계산기 세팅 - Plus // 계산기 세팅 - Minus // 더하기 // 빼기 ``` ![](https://hackmd.io/_uploads/SkvgcOYJa.png) ## 02. Composite ### 📝 설명 #### Composite > 1. 형용사 합성의 > 2. 명사 합성물 > 출처: 옥스퍼드 영한사전 > 컴포지트 패턴(Composite pattern)이란 객체들의 관계를 **트리 구조**로 구성하여 부분-전체 계층을 표현하는 패턴으로, 사용자가 단일 객체와 복합 객체 모두 동일하게 다루도록 한다. - 위키백과 #### Composite Pattern의 구성요소 - Component : 공통적으로 수행할수 있는 기능을 가진 프로토콜 - Leaf: Component 를 채택하고 있고, 하위 트리가 없는 객체 - Composite : Component 를 채택하고 있고, Component 타입의 리스트를 가진 객체로서 해당 리스트에는 Leaf 및 Composite가 들어올수 있습니다. #### Composite Pattern이 되기 위한 조건 - `Leaf`와 `Composite`가 동일한 프로토콜을 채택하고 있어야 한다. - 해당 프로토콜이 `func`를 가지고 있어야 한다. - `[Component]`를 가지고 있는 타입은 메서드 내에 리스트를 순회하며 프로토콜을 준수하는 메서드를 호출하는 로직을 가지고 있어야 한다. ### 📎 예제 코드 ```swift protocol Animal { var name: String { get set } func makeSound() } struct Dog: Animal { var name: String func makeSound() { print("\(name) : 왕왕!") } } struct Cat: Animal { var name: String func makeSound() { print("\(name) : 야옹~") } } struct Chick: Animal { var name: String func makeSound() { print("\(name) : 삐약삐약") } } struct AnimalFarm: Animal { var name: String var animals: [Animal] func makeSound() { print("\(name)의 동물들이 내는 소리") animals.forEach { $0.makeSound() } } } let dog1 = Dog(name: "멍멍이") let dog2 = Dog(name: "바둑이") let dog3 = Dog(name: "흰둥이") let cat1 = Cat(name: "야옹이") let cat2 = Cat(name: "나비") let cat3 = Cat(name: "식빵") let chick1 = Chick(name: "삐약이") let chick2 = Chick(name: "꼬꼬") let chick3 = Chick(name: "피닉스") let bmoFarm = AnimalFarm(name: "비모 농장", animals: [dog1, cat1, chick1]) let serenaFarm = AnimalFarm(name: "세레나 농장", animals: [dog2, cat2, chick2]) let maxFarm = AnimalFarm(name: "맥스 농장", animals: [dog3, cat3, chick3]) let farms = AnimalFarm(name: "농장 연합", animals: [bmoFarm, serenaFarm, maxFarm]) farms.makeSound() // 농장 연합의 동물들이 내는 소리 // 비모 농장의 동물들이 내는 소리 // 멍멍이 : 왕왕! // 야옹이 : 야옹~ // 삐약이 : 삐약삐약 // 세레나 농장의 동물들이 내는 소리 // 바둑이 : 왕왕! // 나비 : 야옹~ // 꼬꼬 : 삐약삐약 // 맥스 농장의 동물들이 내는 소리 // 흰둥이 : 왕왕! // 식빵 : 야옹~ // 피닉스 : 삐약삐약 ``` ### ✅ 장점 - 한 번의 호출로 최하단의 코드까지 실행이 가능하다. - 실행 로직이 단순하다. - 새로운 `Leaf`나 `Composite` 작성이 쉽다. - 트리의 중간 객체가 수정, 삭제되어도 코드 변경에 큰 부담이 없다. ### ⚠️ 한계점 - 트리구조가 아니라면 사용할 수 없다. - 트리가 커질수록 객체 관계가 복잡해진다. - 실행 순서를 보장하고 싶은 경우 트리구조가 명확해야 한다. <details> <summary>한계점 예시 접기/펼치기</summary> ```swift // 비모농장이 농장 연합에서 나오고 싶지만 어딘가에 적을 대고 싶은 경우 let bmoFarm = AnimalFarm(name: "비모 농장", animals: [dog1, cat1, chick1]) let serenaFarm = AnimalFarm(name: "세레나 농장", animals: [bmoFarm, dog2, cat2, chick2]) // 세레나의 동물들 중 첫번째 요소로 비모농장이 들어갔다. let maxFarm = AnimalFarm(name: "맥스 농장", animals: [dog3, cat3, chick3]) let farms = AnimalFarm(name: "농장 연합", animals: [serenaFarm, maxFarm]) // 비모 농장은 없다. farms.makeSound() // 농장 연합의 동물들이 내는 소리 // 세레나 농장의 동물들이 내는 소리 // 비모 농장의 동물들이 내는 소리 // 멍멍이 : 왕왕! // 야옹이 : 야옹~ // 삐약이 : 삐약삐약 // 바둑이 : 왕왕! // 나비 : 야옹~ // 꼬꼬 : 삐약삐약 // 맥스 농장의 동물들이 내는 소리 // 흰둥이 : 왕왕! // 식빵 : 야옹~ // 피닉스 : 삐약삐약 ``` </details> 👉 한 번 호출로 동일한 타입의 동일한 메서드를 모두 실행시키고 싶은 경우 사용한다. ## 03. Facade ### 📝 설명 #### Facade(Façade) > 외관, **건물의 출입구**로 이용되는 정면 외벽 부분 > #### 소프트웨어의 다른 커다란 코드 부분에 대하여 간략화된 인터페이스를 제공해주는 디자인 패턴 - 사용하기 복잡한 기능들을 하나의 **단순화된 창구를 통해 실행** - 앱에서 실행할 로직이나 메서드들을 간편한 인터페이스로 재정리 → Facade 인스턴스에서 한번에 모아 실행 - **클라이언트는 복잡한 시스템을 알 필요 없이 단순한 인터페이스를 이용** - 클라이언트와 하위 시스템이 서로 긴밀하게 연결되지 않도록 함 - Additional Facade 활용 가능 - Facade가 반드시 하나만 있어야 한다는 규칙이나 제한은 없음. 시스템의 요소마다 Facade 패턴을 적용하고 다시 그 Facade를 합친 Facade를 만드는 식으로 재귀적인 적용을 할 수 있음 - Facade 타입의 위치나 위임 형식 등에 제한이 없음 - 퍼사드 타입을 만들어 적절히 기능만 모아주면 됨 ### 📎 예제 코드 ```swift protocol CamperFacade { func doProject() } struct Rest { func doNothing() { print("멍...") } func sleep() { print("zzz") } func thinkAboutSomethingElse() { print("oO(복권 당첨되고 싶당)") } } struct Coding { func designStructure() { print("구상: 이렇게 만들면 되겠지") } func writeCode() { print("코드 작성: 나 천재인듯") } func testCode() { print("코드 테스트: 어라 이게 아닌데") } func fixCode() { print("버그 픽스: 내 길이 아닌가") } } struct Camper: CamperFacade { let rest = Rest() let coding = Coding() let name: String init(_ name: String) { self.name = name } func doProject() { print("===\(name) 프로젝트 시작===") coding.designStructure() coding.writeCode() rest.sleep() coding.testCode() rest.doNothing() coding.fixCode() rest.thinkAboutSomethingElse() coding.fixCode() rest.sleep() coding.writeCode() print("===\(name) 프로젝트 종료===") } } let camper = Camper("맥스") camper.doProject() // ===맥스 프로젝트 시작=== // 구상: 이렇게 만들면 되겠지 // 코드 작성: 나 천재인듯 // zzz // 코드 테스트: 어라 이게 아닌데 // 멍... // 버그 픽스: 내 길이 아닌가 // oO(복권 당첨되고 싶당) // 버그 픽스: 내 길이 아닌가 // zzz // 코드 작성: 나 천재인듯 // ===맥스 프로젝트 종료=== ``` ### 📍 사용 시기 - 시스템이 너무 복잡해서 간단한 인터페이스를 통해 접근방식을 제한하고 싶을 때 - 하위 시스템이 외부와 결합도가 너무 높을 때(의존성을 낮춰야 할 때) ### ✅ 장점 - 외부에서 시스템을 사용하기 쉬워짐 - 복잡한 코드를 감춰서 클라이언트가 하위 시스템 코드를 몰라도 됨 - 하위 시스템 간의 결합도가 높을 때 이를 감소시키고 한 곳으로 모을 수 있음 ### ⚠️ 한계점 - 앱 개발 초기 단계 등 설계가 복잡하지 않은 경우는 사용하기가 애매함 - 퍼사드가 앱의 모든 클래스에 결합되어 퍼사드에 대한 의존도가 너무 높아질 수 있음 → 결국 의존성을 완전히 피할 수는 없다 - Facade 패턴을 사용하기 위해 새로운 계층이 추가되면 관리할 타입이 하나 더 생성됨 → 유지보수 측면에서 공수 발생 👉 추상화하고자 하는 시스템의 복잡도, 그리고 퍼사드를 통해 얻게되는 이점과 유지보수 비용을 비교하며 결정해야 함 ---- ### 참고링크 - [📖 위키백과 - 컴포지트 패턴](https://ko.wikipedia.org/wiki/컴포지트_패턴) - [📖 위키백과 - 데코레이터 패턴](https://ko.wikipedia.org/wiki/데코레이터_패턴) - [📖 위키백과 - 퍼사드 패턴](https://ko.wikipedia.org/wiki/퍼사드_패턴) - [🐻 야곰닷넷 - Decorator](https://yagom.net/courses/design-pattern-in-swift/lessons/%ea%b5%ac%ec%a1%b0-%ed%8c%a8%ed%84%b4/topic/decorator/) - [🐻 야곰닷넷 - Composite](https://yagom.net/courses/design-pattern-in-swift/lessons/%ea%b5%ac%ec%a1%b0-%ed%8c%a8%ed%84%b4/topic/composite/) - [🐻 야곰닷넷 - Facade](https://yagom.net/courses/design-pattern-in-swift/lessons/구조-패턴/topic/facade/)