Pola Arsitektur Iterator di Swift Universe

"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 // Another requirements go here… } 

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? { // TODO: Return next underlying Book element. } } extension Shelf: Sequence { func makeIterator() -> ShelfIterator { return ShelfIterator(books: books) } } 

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) /* 1. .  –  2. .  –   3. .  –   */ 

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:

Source: https://habr.com/ru/post/id437614/


All Articles