"Iterator" adalah salah satu pola desain yang paling sering tidak diperhatikan oleh programer, karena implementasinya, sebagai suatu peraturan, dibangun langsung ke dalam alat standar bahasa pemrograman. Namun, ini juga merupakan salah satu pola perilaku yang dijelaskan dalam
buku "Geng Empat", "GoF", "Pola Desain: Elemen Perangkat Lunak Berorientasi Objek Reusable" , dan memahami perangkatnya tidak pernah sakit, dan kadang-kadang bahkan dapat membantu dalam sesuatu.
"Iterator" adalah metode akses sekuensial ke semua elemen objek komposit (khususnya, tipe wadah, seperti array atau set).Alat bahasa standar
Buat beberapa jenis
array :
let numbersArray = [0, 1, 2]
... dan kemudian "berjalan" melalui
siklus :
for number in numbersArray { print(number) }
... sepertinya tindakan yang sangat alami, terutama untuk bahasa pemrograman modern seperti
Swift . Namun, di balik layar tindakan sederhana ini adalah kode yang menerapkan prinsip-prinsip pola Iterator.
Dalam "Swift", untuk dapat "beralih" variabel menggunakan
for
sepeda, tipe variabel harus menerapkan
protokol Sequence
. Antara lain, protokol ini mensyaratkan tipe untuk memiliki
Iterator
tipe
associatedtype
, yang pada gilirannya harus menerapkan persyaratan protokol
IteratorProtocol
Protokol, serta
metode pabrik makeIterator()
, yang mengembalikan "iterator" spesifik untuk tipe ini:
protocol Sequence { associatedtype Iterator : IteratorProtocol func makeIterator() -> Self.Iterator
Protokol
IteratorProtocol
, pada gilirannya, hanya berisi satu metode -
next()
, yang mengembalikan elemen berikutnya dalam urutan:
protocol IteratorProtocol { associatedtype Element mutating func next() -> Self.Element? }
Kedengarannya seperti "banyak kode rumit," tetapi sebenarnya tidak. Di bawah ini kita akan melihat ini.
Tipe
Array
mengimplementasikan protokol
Sequence
(meskipun tidak secara langsung, tetapi melalui rantai
inheritance protokol :
MutableCollection
mewarisi persyaratan
Collection
, dan yang terakhir mewarisi persyaratan
Sequence
), jadi instance-nya, khususnya, dapat "diiterasi" menggunakan -sepeda.
Jenis khusus
Apa yang perlu dilakukan untuk bisa mengulang tipe Anda sendiri? Seperti yang sering terjadi, paling mudah untuk menunjukkan contoh.
Misalkan ada tipe yang mewakili rak buku yang menyimpan sekumpulan instance kelas tertentu, yang pada gilirannya mewakili sebuah buku:
struct Book { let author: String let title: String } struct Shelf { var books: [Book] }
Untuk dapat "mengulang" instance kelas
Shelf
, kelas ini harus memenuhi persyaratan protokol
Sequence
. Untuk contoh ini, cukup dengan mengimplementasikan metode
makeIterator()
, terutama karena persyaratan protokol lainnya memiliki
implementasi standar . Metode ini harus mengembalikan instance dari jenis yang mengimplementasikan protokol
IteratorProtocol
. Untungnya, dalam kasus Swift, ini adalah kode yang sangat sederhana:
struct ShelfIterator: IteratorProtocol { private var books: [Book] init(books: [Book]) { self.books = books } mutating func next() -> Book? {
Metode
next()
dari tipe
ShelfIterator
dinyatakan
mutating
, karena instance type harus menyimpan keadaan yang terkait dengan iterasi saat ini:
mutating func next() -> Book? { defer { if !books.isEmpty { books.removeFirst() } } return books.first }
Opsi implementasi ini selalu mengembalikan elemen pertama dalam urutan, atau
nil
jika urutan kosong. Blok
defer
"dibungkus" dengan kode untuk mengubah koleksi iterated, yang menghilangkan elemen dari langkah iterasi terakhir segera setelah metode kembali.
Contoh penggunaan:
let book1 = Book(author: ". ", title: "") let book2 = Book(author: ". ", title: " ") let book3 = Book(author: ". ", title: " ") let shelf = Shelf(books: [book1, book2, book3]) for book in shelf { print("\(book.author) β \(book.title)") }
Karena semua tipe yang digunakan (termasuk
Array
mendasari
Shelf
) didasarkan pada
semantik nilai (sebagai lawan dari referensi) , Anda tidak perlu khawatir tentang nilai variabel asli yang diubah selama iterasi. Saat menangani jenis berdasarkan semantik tautan, titik ini harus diingat dan diperhitungkan saat membuat iterator Anda sendiri.
Fungsionalitas klasik
"Iterator" klasik yang dijelaskan dalam buku "Gangs of Four", selain mengembalikan elemen berikutnya dari urutan iterable, juga dapat setiap saat mengembalikan elemen saat ini dalam proses iterasi, elemen pertama dari urutan iterable dan nilai "flag" yang menunjukkan apakah masih ada elemen dalam urutan berulang relatif terhadap langkah iterasi saat ini.
Tentu saja, akan mudah untuk mendeklarasikan protokol sehingga memperluas kemampuan standar
IteratorProtocol
:
protocol ClassicIteratorProtocol: IteratorProtocol { var currentItem: Element? { get } var first: Element? { get } var isDone: Bool { get } }
Elemen pertama dan saat ini dikembalikan opsional sejak urutan sumber mungkin kosong.
Opsi implementasi sederhana:
struct ShelfIterator: ClassicIteratorProtocol { var currentItem: Book? = nil var first: Book? var isDone: Bool = false private var books: [Book] init(books: [Book]) { self.books = books first = books.first currentItem = books.first } mutating func next() -> Book? { currentItem = books.first books.removeFirst() isDone = books.isEmpty return books.first } }
Dalam uraian asli pola, metode
next()
mengubah keadaan internal iterator untuk pergi ke elemen berikutnya dan bertipe
Void
, dan elemen saat ini dikembalikan oleh metode
currentElement()
. Dalam protokol
IteratorProtocol
, kedua fungsi ini seolah-olah digabungkan menjadi satu.
Kebutuhan akan metode
first()
juga diragukan, karena iterator tidak mengubah urutan asli, dan kami selalu memiliki kesempatan untuk mengakses elemen pertamanya (jika ada, tentu saja).
Dan, karena metode
next()
mengembalikan
nil
ketika iterasi selesai, metode
isDone()
juga menjadi tidak berguna.
Namun, untuk tujuan akademik, sangat mungkin untuk membuat fungsi yang dapat menggunakan fungsionalitas penuh:
func printShelf(with iterator: inout ShelfIterator) { var bookIndex = 0 while !iterator.isDone { bookIndex += 1 print("\(bookIndex). \(iterator.currentItem!.author) β \(iterator.currentItem!.title)") _ = iterator.next() } } var iterator = ShelfIterator(books: shelf.books) printShelf(with: &iterator)
Parameter
iterator
dinyatakan
inout
karena keadaan internal berubah selama eksekusi fungsi. Dan ketika fungsi dipanggil, instance iterator ditransmisikan tidak secara langsung oleh nilainya sendiri, tetapi dengan referensi.
Hasil pemanggilan metode
next()
tidak digunakan, mensimulasikan tidak adanya nilai kembali implementasi buku teks.
Alih-alih sebuah kesimpulan
Sepertinya ini yang ingin saya katakan kali ini. Semua kode indah dan sengaja menulisnya!
Artikel saya yang lain tentang pola desain: