
컴포지트 패턴이란 객체들의 관계를 트리 구조로 구성하여 부분-전체 계층을 표현하는 패턴으로, 사용자가 단일 객체와 복합 객체 모두 동일하게 다루도록 한다.
- 군대에서 대대장이 중대장에게 명령하고, 중대장이 소대장들에게 명령을 하고, 소대장이 소대원들에게 모두 같은 명령을 하달해서 전 부대원이 해당 명령을 수행하게 되는 것
- 사용하는 폴더의 용량을 알고 싶을 때 du -h 커맨드 입력을 통해 파일정보를 알 수 있다. 이때 최상위 폴더에서 해당 옵션을 입력한다면 하위에 있는 모든 폴더, 파일들의 정보를 한 번에 확인할 수 있기 때문에 우리는 최하위 폴더까지 일일이 커맨드를 입력하지 않아도 되는 것

- 공통적으로 수행할 수 있는 기능을 가진 프로토콜
- Component를 채택하고 있고, 하위 트리가 없는 객체
- Component를 채택하고 있고, Component 타입의 리스트를 가진 객체로서 해당 리스트에는 Leaf 및 Composite가 들어 올 수 있다.
import UIKit
// Component
protocol Shape: AnyObject {
func draw()
}
// Leaf
class Circle: Shape {
func draw() {
print("Draw a circle")
}
}
class Square: Shape {
func draw() {
print("Draw a Square")
}
}
// Composite
class Drawin: Shape {
private var shapes: [Shape] = []
func draw() {
for shape in shapes {
shape.draw()
}
}
func addShape(shape: Shape) {
shapes.append(shape)
}
func removeShape(shape: Shape) {
shapes = shapes.filter { $0 !== shape }
}
}
let drawin = Drawin()
drawin.addShape(shape: Circle())
drawin.addShape(shape: Circle())
drawin.addShape(shape: Square())
drawin.addShape(shape: Square())
drawin.addShape(shape: Circle())
drawin.draw()
import Foundation
// Component
protocol DataSource {
func fetchData(completion: @escaping (Data?) -> Void)
}
// Leaf
class NetworkDataSource: DataSource {
let url: URL
init(url: URL) {
self.url = url
}
func fetchData(completion: @escaping (Data?) -> Void) {
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
completion(data)
}
task.resume()
}
}
class CacheDataSource: DataSource {
var data: Data?
func fetchData(completion: @escaping (Data?) -> Void) {
completion(Data)
}
}
// Composite
class CompositeDataSource: DataSource {
let networkDataSource: NetworkDataSource
let cacheDataSource: CacheDataSource
init(networkDataSource: NetworkDataSource, cacheDataSource: CacheDataSource) {
cacheDataSource.fetchData { [weak self] data in
if let data = data {
completion(data)
} else {
self?.networkDataSource.fetchData { data in
self?.cacheDataSource.data = data
completion(data)
}
}
}
}
}
// Usage
let url = URL(string: "https://example.com")!
let networkDataSource = NetworkDataSource(url: url)
let cacheDataSource = CahceDataSource()
let compositeDataSource = CompositeDataSource(
networkDataSource: networkDatasource,
cahceDataSource: cacheDataSource
)
compositeDataSource.fetchData { data in
// Handle Data
}
FeedLoader 인터페이스는 Composite 패턴의 Component 역할을 합니다. 이 인터페이스는 RemoteFeedLoader와 LocalFeedLoader (Leaf 역할) 그리고 RemoteWithLocalFallbackFeedLoader (Composite 역할)에 의해 구현된다.RemoteWithLocalFallbackFeedLoader는 FeedLoader 인터페이스를 채택하면서 동시에 RemoteFeedLoader와 LocalFeedLoader를 포함하고 있다. RemoteWithLocalFallbackFeedLoader는 단일 FeedLoader이면서 동시에 여러 FeedLoader를 포함하고 있는 복합 객체이다. FeedLoader 인터페이스를 통해 단일 객체와 복합 객체를 동일하게 다룰 수 있다.import UIKit
//typealias FeedLoader = (([String]) -> Void) -> Void
// Interface
protocol FeedLoader {
func loadFeed(completion: @escaping ([String]) -> Void)
}
class FeedViewController: UIViewController {
var loader: FeedLoader!
convenience init(loader: FeedLoader) {
self.init()
self.loader = loader
}
override func viewDidLoad() {
super.viewDidLoad()
loader.loadFeed { loadedItems in
// update UI
}
}
}
// leaf
class RemoteFeedLoader: FeedLoader {
func loadFeed(completion: @escaping ([String]) -> Void) {
print("remote")
}
}
class LocalFeedLoader: FeedLoader {
func loadFeed(completion: @escaping ([String]) -> Void) {
print("local")
}
}
struct Reachability {
static let networkAvailable = false
}
// Composite
// concrete type은 최대한 밖으로 빼기
class RemoteWithLocalFallbackFeedLoader: FeedLoader {
let remote: RemoteFeedLoader
let local: LocalFeedLoader
init(remote: RemoteFeedLoader, local: LocalFeedLoader) {
self.remote = remote
self.local = local
}
func loadFeed(completion: @escaping ([String]) -> Void) {
// if문을 위 struct 변수를 활용하여 훨씬 더 간단하게 코드 작성
// if Reachability.networkAvailable {
// remote.loadFeed { loadedItems in
// // do something
// }
// } else {
// local.loadFeed { loadedItems in
// // do something
// }
// }
let load = Reachability.networkAvailable ? remote.loadFeed : local.loadFeed
load(completion)
}
}
let vc = FeedViewController(loader: RemoteFeedLoader())
let vc2 = FeedViewController(loader: LocalFeedLoader())
let vc3 = FeedViewController(loader: RemoteWithLocalFallbackFeedLoader(remote: RemoteFeedLoader(), local: LocalFeedLoader()))
vc3.loader.loadFeed { items in
}
제가 학습한 내용을 요약하여 정리한 것입니다. 내용에 오류가 있을 수 있으며, 어떠한 피드백도 감사히 받겠습니다.
감사합니다