구조 패턴 - 복합체 패턴 (Composite Pattern)

French Marigold·2024년 3월 26일
2

디자인패턴

목록 보기
3/10
post-thumbnail

정의

  • 복합체 패턴 (Composite Pattern)이란 전체 객체를 마치 하나의 객체인 것처럼 조작할 수 있도록 객체 집합을 트리 구조로 그룹화하는 패턴이다. 쉽게 말해 “일괄적인 관리” 가 가능한 것이다.
  • A라는 회사가 있다고 가정해보자. 회사가 잘 돌아가려면 구조 체계가 명확히 잡혀 있어야 한다. 하위 계층에는 직원들과 여러 팀 단위가 존재할 것이고, 그 팀 안에는 또 직원들과 여러 팀 단위가 존재할 것이다. 이러한 구조를 “트리 구조” 라고 한다.

  • A 회사가 이제 직원들에게 중요한 공지사항 이메일을 보내고자 한다. 그런데 일일이 직원들에게 공지사항 이메일을 보내는 것은 굉장히 시간 낭비이지 않을까? 언제 일일이 직원들에게 이메일을 보내고 있겠나?
  • 이럴 때 필요한 것이 바로 복합체 패턴 (Composite Pattern)이다. 복합체 패턴은 “프로토콜 하나 (Components)”를 이용해 모든 그룹들을 마치 하나의 객체처럼 관리할 수 있게끔 한다.

복합체 패턴 (Composite Pattern) 의 구조

  • Components
    • 복합체 패턴에 존재하는 Leaf와 Composite 객체 모두에 접근할 수 있는 프로토콜을 생성한다.
  • Leaf
    • 하위 계층 객체가 없는 가장 기본적인 단위의 객체.
    • Components 프로토콜을 채택한 후 내부를 구현한다.
  • Composite
    • Leaf와 Composite를 하위 계층으로 갖는 객체.
    • 하위 계층들의 내부는 자세히 알 수 없지만 Components 프로토콜을 통해 하위 계층들에 접근하여 원하는 값들을 얻어 올 수 있다.
  • Client
    • Components 프로토콜을 이용해 복합체 패턴에 존재하는 모든 객체를 사용하고자 하는 객체.

복합체 패턴 적용 과정 예시

  1. 다시 A 회사 이야기로 돌아와서, A 회사가 직원들에게 일일이 이메일을 보내는 것이 아니라 모든 직원들에게 EmailSendingSystem 프로토콜에 가입하도록 한다. 이것을 Components 프로토콜이라고 한다.
// Components - 복합체 패턴에 존재하는 Leaf와 Composite 객체 
// 모두에 접근할 수 있는 프로토콜을 생성한다. 
protocol EmailSendingSystem {
    func sendEmail(content: String)
}
  1. 직원은 반드시 회사의 EmailSendingSystem에 동의해야 한다. 직원에게는 하위 계층이 없으며 하위 계층이 없는 객체를 Leaf라고 한다.
// Leaf - 하위 계층 객체가 없는 가장 기본적인 객체
final class Employee: EmailSendingSystem {
    
    // 사원의 이름
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func sendEmail(content: String) {
        print("\(self.name)님에게 이메일을 보냄 : \(content)")
    }
}
  1. 팀 또한 회사의 EmailSendingSystem에 동의해야 한다. 팀에게는 직원들이나 다른 팀들이라는 하위 계층이 존재한다. 하위 계층이 존재하는 객체를 Composite라고 한다.
// Composite - Leaf와 Composite을 하위 객체로 갖는 객체.
final class Team: EmailSendingSystem {
    
    // 팀 안에는 사원들과 또 다른 팀들로 구성이 되어 있을 수 있음.
    var teams: [EmailSendingSystem] = []
    
    // 팀의 이름
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func add(_ worker: EmailSendingSystem) {
        teams.append(worker)
    }
    
    func sendEmail(content: String) {
        // 팀 안에 있는 팀원들에게 차례대로 이메일을 보냄
        for team in teams {
            team.sendEmail(content: content)
        }
    }
}
  1. Client란 복합체 패턴 내에 있는 모든 객체들을 관리하고 Components 프로토콜을 이용해 실행하는 영역이다. 회사 이메일 시스템에 들어와 있는 모든 팀들과 사원들을 여기에 저장하며 모든 이메일 시스템을 관리하는 영역이다.
// Client - 복합체 패턴 내에 있는 모든 객체들을 관리하고 Components 프로토콜을 이용해 실행하는 영역

// 현재 이메일 시스템에 들어와 있는 모든 객체들을 여기에 저장
final class EmailSystemManager {
    
    // 팀 단위
    let iOSTeam = Team(name: "iOS")
    
    // 사원 단위
    let leo = Employee(name: "레오")
    let elly = Employee(name: "엘리")
    let jimmy = Employee(name: "지미")
    let sam = Employee(name: "샘")
    let marigold = Employee(name: "메리골드")
    
    // 팀을 구성
    private func makeTeam() {
        iOSTeam.add(leo)
        iOSTeam.add(elly)
        iOSTeam.add(jimmy)
        iOSTeam.add(sam)
        iOSTeam.add(marigold)
    }
    
    // 사원 중 아무나에게 개인적으로 이메일을 보낼 수 있음
    func sendEmailTo(employee: Employee, content: String) {
        employee.sendEmail(content: content)
    }
    
    // 모두에게 이메일 보내기
    func sendEmailToAll(content: String) {
        // 팀을 구성한 뒤
        makeTeam()
        // 팀에게 이메일을 보내면 팀원 안에 있는 모든 사람들에게 이메일이 감 ⭐️⭐️
        iOSTeam.sendEmail(content: content)
    }
}
  1. “모두에게 이메일 보내기” 버튼을 누르면 모두에게 공지사항을 보내는 로직을 실행한다.
import SwiftUI

struct SendingEmailView: View {
    var body: some View {
        ZStack {
            Color.sendingEmailBackground
                .ignoresSafeArea(.all)
            
            makeEmailSendButton()
                .padding()
        }
    }
    
    private func makeEmailSendButton() -> some View {
        return sendEmail()
    }
    
    private func sendEmail() -> some View {
        let emailSystemManager = EmailSystemManager()
        
        return Button {
            emailSystemManager.sendEmailToAll(content: "모두에게 알립니다!")
        } label: {
            Label("모두에게 이메일 보내기", systemImage: "envelope")
        }
        .buttonStyle(.borderedProminent)
        .tint(.sendingEmailBrown)
    }
}

패턴 사용 시기

  • 트리구조 (그룹 내에 또다른 하위 계층이 존재하는 객체) 를 한 번에 관리하고자 할 때.
    • 대표적으로 회원들에게 공지사항 이메일을 전송하고자 할 때이다. 즉, 일일이 회원들에게 이메일을 보내는 것이 아닌 한 번에 그룹으로 묶어 모두에게 메일을 전송하고자 할 때 사용하면 좋다.

패턴의 장점

  • OCP를 만족한다 ⇒ 기존에 작동하는 트리 구조 코드를 직접 손대지 않고도 Components 프로토콜을 이용해 기능을 추가 및 확장시킬 수 있다.
  • 복잡한 트리 구조를 보다 간편하게 관리할 수 있다.

패턴의 단점

  • 트리의 Depth가 길어질수록 라인 단위의 디버깅에 어려움이 생긴다.

참고 문헌

profile
꽃말 == 반드시 오고야 말 행복

4개의 댓글

comment-user-thumbnail
2024년 3월 30일

클라이언트 입장에서는 내부에서 어떻게 지지고 볶아서 전달하든 상관 없이 한 곳에만 call을 하면 되는 거군요

1개의 답글
comment-user-thumbnail
2024년 3월 30일

적절한 이미지와 예시 덕분에 새로운 개념을 쉽게 이해할 수 있던 거 같아요!
내용도 간결하게 정리 잘해주셔서 감사합니당🍎

1개의 답글