요즘 회사 스터디를 'Java 언어로 배우는 디자인 패턴 입문' 책으로 진행하고 있습니다.
예전에 읽었던 책이지만 스위프트로 다시 구현해보면 더 체득할 수 있는 것이 많겠다는 생각이 들었습니다.
(예전에 썼던 글 홍보)
Iterator는 for i in array 에서 i를 추상화해서 사용하는 패턴입니다.
클라이언트는 컬렉션 요소를 직접 다루지 않고 데이터를 순회할 수 있습니다.
책의 예제 구현해보기
그럼 책의 예제를 함께 구현해보겠습니다.
결과물 코드는 github.com/cozzin/DesignPattern/tree/main/DesignPattern/Iterator 에 있습니다.
먼저 테스트 코드를 작성합니다.
선반에 책을 하나도 넣지 않았을 때, 결과도 비어있어야 합니다.
컴파일 에러를 해결하기 위해서 객체를 하나씩 등록해보겠습니다.
Iterator 프로토콜을 선언하기 위해서 작성하던 중에 Swift Collection에 이미 IteratorProtocol이 있다는 것을 알게 되었습니다.
제가 만들려던 Iterator와 동일한 의도로 만들어진 프로토콜 이었습니다.
developer.apple.com/documentation/swift/iteratorprotocol
var animalIterator = animals.makeIterator()
while let animal = animalIterator.next() {
print(animal)
}
이렇게 간편하게 iterator를 만들어서 사용할 수 있습니다ㅎ;;;
하지만!!! 원리 파악을 위해 구현하는 거라서 일단 Iterator 프로토콜을 직접 만들어서 쓰겠습니다.
protocol Iterator {
associatedtype Value
func hasNext() -> Bool
func next() -> Value
}
책에서는 next() -> Object로 표현했는데, 이는 클라이언트에서 사용할 때 타입 캐스팅을 유발합니다.
asscociatedtype으로 타입을 지정해주도록 하면 클라이언트에서 구체적인 타입을 사용할 수 있습니다.
Aggregate는 Iterator를 생성할 수 있는 역할입니다.
protocol Aggregate {
associatedtype Iterator
func iterator() -> Iterator
}
이제 구체적인 구현체를 만들어보겠습니다.
struct Book {
let name: String
}
인터페이스만 따르고 구현 내용은 대충 채워둡니다.
final class BookShelfIterator: Iterator {
typealias Value = Book
private let bookShelf: BookShelf
private var index: Int
init(bookShelf: BookShelf) {
self.bookShelf = bookShelf
self.index = 0
}
func hasNext() -> Bool {
return false
}
func next() -> Book {
return Book(name: "")
}
}
final class BookShelf: Aggregate {
typealias Iterator = BookShelfIterator
private let maxSize: Int
init(maxSize: Int) {
self.maxSize = maxSize
}
func iterator() -> Iterator {
Iterator(bookShelf: self)
}
}
그러고 나면 테스트코드가 통과됩니다.
이번에는 책 한권을 사용하는 테스트를 추가해보겠습니다.
BookShelf에 append 메소드가 없기 때문에 추가합니다.
final class BookShelf: Aggregate {
typealias Iterator = BookShelfIterator
private var books: [Book]
private let maxSize: Int
init(maxSize: Int) {
self.books = []
self.maxSize = maxSize
}
func append(_ book: Book) {
books.append(book)
}
func iterator() -> Iterator {
Iterator(bookShelf: self)
}
}
테스트를 실행해보면 빌드는 되지만 테스트는 실패합니다.
results가 비어있습니다. 여기가 제대로 채워지려면 BookShelfIterator를 제대로 구현해야 합니다.
BookShelf에 두가지 기능이 추가하고
final class BookShelf: Aggregate {
var count: Int {
return books.count
}
func book(at index: Int) -> Book {
return books[index]
}
}
이 기능을 활용해서 BookShelfIterator의 남은 부분을 구현합니다.
final class BookShelfIterator: Iterator {
func hasNext() -> Bool {
return index < bookShelf.count
}
func next() -> Book {
let book = bookShelf.book(at: index)
index += 1
return book
}
}
그러면 테스트 코드가 통과합니다.
책이 여러개 있을 때도 테스트 합니다.
기능이 제대로 구현되었습니다!!!
테스트 과정을 다 기록하면서 진행을 했는데, 번거롭기는 하지만 원하는 기능을 점진적으로 만드는데 도움이 되었습니다.
제네릭 특성을 살려서 책의 예제보다 iterator를 더 안전하게 만들어서 만족스러웠습니다. 뿌듯-!
'스터디' 카테고리의 다른 글
[디자인패턴] 전광판을 예시로 풀어본 Bridge 패턴 (0) | 2021.05.24 |
---|---|
[엘레강트 오브젝트] '생성자에 코드를 넣지 마세요' 적용해보기 (0) | 2021.05.19 |
[디자인패턴] Builder 패턴 (1) | 2021.05.18 |
[디자인패턴] Template Method 패턴 (1) | 2021.05.05 |