apple/DesignPattern & Architecture

[Swift] Composite Pattern

lgvv 2022. 6. 15. 18:26

Composite Pattern

✅ Composite Pattern

 

아래의 문서를 구입하여 영어 문서를 번역하고 이해한 것을 바탕으로 글을 작성하고 있습니다.

https://www.raywenderlich.com/books/design-patterns-by-tutorials/v3.0/chapters/20-composite-pattern

 

Design Patterns by Tutorials, Chapter 20: Composite Pattern

This is a structural pattern that groups a set of objects into a tree so that they may be manipulated as though they were one object. If your app's class hierarchy forms a branching pattern, trying to create two types of classes for branches and nodes can

www.raywenderlich.com

 

composite 패턴은 객체 집합이 하나의 객체인 것처럼 조작될 수 있도록 객체 집합을 트리 구조로 그룹화하는 패턴입니다.

1. component protocol은 프로토콜은 모든 구조가 동

일한 방식으로 처리될 수 있게끔 보장합니다.

2. 리프(leaf)는 child 요소가 없는 component입니다.

3. composite은 leaf 객체와 composite를 담을 수 있는 컨테이너 입니다.

 

composite 패턴

 

composite 복합체와 leaf 노드 모두 compnent 프로토콜에서 파생됩니다. composite 객체에서 여러 개의 다른 leaf 클래스를 보유할 수도 있습니다.

 

예를 들어서 array는 composite(복합체)입니다. component는 Array그 자체입니다. composite는 array에서 private container를 포함하는 데 사용되는 개인 container입니다. 각 리프는 Int, String 또는 array에 추가하는 것과 같은 concrete 타입입니다. 

 

When should you use it?

앱의 클래스 계층이 분기 패턴을 형성하는 경우 분기 및 노드에 대해 두가지 유형의 클래스를 만들려고 하면 해당 클래스가 통신하기 어려울 수 있습니다. 이 문제는 프로토콜에 따라 분기 및 노드를 동일하게 처리하여 composite 패턴으로 해결할 수 있습니다. 이렇게 하면 model 에 추상화 계층이 추가되고 궁극적으로 모델의 복잡성이 줄어들게 됩니다.

 

Playground example

예시 -:) 파일 계층은 composite 패턴의 일상적인 예시 입니다. 파일과 폴더에 대해 생각해 보세요! 모든 .mp3 및 .jpeg 파일 및 폴더는 많은 기능을 공유하고 있습니다. 예를 들면, '열림', '휴지통으로 이동', '정보 가져오기', '검색' 등을 선택합니다. 

서로 다른 파일 그룹이 모두 component protocol을 준수하기 때문에 같은 유형이 아니더라도 다른 파일 그룹을 이동하고 저장할 수 있습니다. 

 

링크의 예시도 매우 휼룡합니다. 하지만 다른 블로그 작성자님의 예시도 정말 휼룡하여 이로 대체하였습니다.

참고한 코드의 링크는 하단에 걸어두었습니다.

 

 

import UIKit

protocol FileComponent {
    var size: Int { get set }
    var name: String { get set }
    func open()
    func getSize() -> Int
}

final class Directory: FileComponent {
    var size: Int
    var name: String
    var files: [FileComponent] = []
    func open() {
        print("\(self.name) Directory의 모든 File Open")
        for file in files {
            file.open()
        }
        print("\(self.name) Directory의 모든 File Open 완료\n")
    }
    
    func getSize() -> Int {
        var sum: Int = 0
        for file in files {
            sum += file.getSize()
        }
        return sum
    }
    
    func addFile(file: FileComponent) {
        self.files.append(file)
        self.size += file.size
    }
    
    init(size: Int, name: String) {
        self.size = size
        self.name = name
    }
}

final class MusicFile: FileComponent {
    var size: Int
    var name: String
    var artist: String
    
    func open() {
        print("\(self.name) Music Artist  : \(self.artist)")
    }
    
    func getSize() -> Int {
        return self.size
    }
    
    init(size: Int, name: String, artist: String) {
        self.size = size
        self.name = name
        self.artist = artist
    }
}

final class CodeFile: FileComponent {
    var size: Int
    var name: String
    var language: String
    
    func open() {
        print("\(self.name) Code Language : \(self.language)")
    }
    
    func getSize() -> Int {
        return self.size
    }
    
    init(size: Int, name: String, language: String) {
        self.size = size
        self.name = name
        self.language = language
    }
}

let rootDirectory = Directory(size: 0, name: "root")
let musicDirectory = Directory(size: 0, name: "music")
let codeDirectory = Directory(size: 0, name: "code")

let iuMusic = MusicFile(size: 10, name: "lilac", artist: "IU")
let lgvvMusic = MusicFile(size: 12, name: "lgvvPop", artist: "lgvv")

let keleubFile = CodeFile(size: 3, name: "Kaleun", language: "Swift")
let coreMLFile = CodeFile(size: 5, name: "CoreML", language: "Swift")
let flutterFile = CodeFile(size: 7, name: "Flutter", language: "Dart")

musicDirectory.addFile(file: iuMusic)
musicDirectory.addFile(file: lgvvMusic)

codeDirectory.addFile(file: keleubFile)
codeDirectory.addFile(file: coreMLFile)
codeDirectory.addFile(file: flutterFile)

rootDirectory.addFile(file: musicDirectory)
rootDirectory.addFile(file: codeDirectory)

print("rootDirectory size: \(rootDirectory.getSize())")
print("musicDirectory size: \(musicDirectory.getSize())")x
print("codeDirectory size: \(codeDirectory.getSize())")

 

결과

 

 

 

What should you be careful about?

composite 패턴을 사용하기 전에 앱의 브랜치 구조가 있는지 확인하세요!! 객체가 거의 동일한 코드가 많이 있는 경우 프로토콜에 적합하도록 하는 것은 좋을 생각이지만 프로토콜과 관련된 모든 상황이 composite 객체를 필요로 하는 것은 아닙니다.

 

[장점]

 - leaf와 composite로 구성된 클래스들의 계층 구조를 만듭니다.

 - 클라이언트 코드가 단순해집니다.

 - component 인터페이스를 따르는 새로운 구성 요소는 쉽게 추가할 수 있습니다.

 - 완전히 새로운 객체들은 추가하기가 어렵습니다.

 

https://github.com/lgvv/DesignPattern/tree/main/%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4%EA%B5%AC%ED%98%84/CompositePattern.playground

 

GitHub - lgvv/DesignPattern: ✨ 디자인 패턴을 공부합니다!

✨ 디자인 패턴을 공부합니다! Contribute to lgvv/DesignPattern development by creating an account on GitHub.

github.com

 

Key points

composite 패턴은 객체 세트를 트리로 그룹화하여 하나의 객체인 것처럼 조작할 수 있는 구조 패턴입니다.

앱의 클래스 계층이 분기 패턴을 형성하는 경우 구성 요소 프로토콜에 따라 분기 및 노드를 거의 동일한 객체로 처리할 수 있습니다. 이 프로토콜은 모델에 추상화 계층을 추가하여 모델의 복잡성을 줄일 수 있습니다.

이것은 유사한 기능을 가진 여러 클래스가 있는 앱을 단순화하는데 도움이 되는 휼룡한 패턴입니다. 이를 통해 코드를 더 자주 재사용하고 클래스의 복잡성을 줄일 수 있습니다.

 

파일 계층은 composite 패턴의 일반적인 예시 입니다. 폴더 뿐만 아니라 모든 .mp3 및 .jpeg 파일은 'open', '휴지통으로 이동' 등의 기능을 공유합니다. 서로 다른 파일 그룹이 모두 구성요소 프로토콜을 준수하기 때문에 같은 유형이 아니더라도 서로 다른 파일 그룹을 이동하고 저장할 수 있습니다.

 

 

 

 

(참고)

https://icksw.tistory.com/243

 

[Swift 디자인 패턴] Composite Pattern (컴포지트) - 디자인 패턴 공부 9

안녕하세요 Pingu입니다.🐧 지난 글에서는 구조 패턴 중 Bridge Pattern(브리지)에 대해 알아봤는데요, 이번 글에서는 또 다른 구조 패턴 중 하나인 Composite Pattern(컴포지트)에 대해 알아보도록 하겠습

icksw.tistory.com