컴포지트 패턴이란 객체들의 관계를 트리 구조로 구성하여 부분-전체 계층을 표현하는 패턴으로, 사용자가 단일 객체와 복합 객체 모두 동일하게 다루도록 한다.
- 군대에서 대대장이 중대장에게 명령하고, 중대장이 소대장들에게 명령을 하고, 소대장이 소대원들에게 모두 같은 명령을 하달해서 전 부대원이 해당 명령을 수행하게 되는 것
- 사용하는 폴더의 용량을 알고 싶을 때 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
}
제가 학습한 내용을 요약하여 정리한 것입니다. 내용에 오류가 있을 수 있으며, 어떠한 피드백도 감사히 받겠습니다.
감사합니다