Pola arsitektur "Pengunjung" di alam semesta "iOS" dan "Swift"

"Pengunjung" adalah salah satu pola perilaku yang dijelaskan dalam buku teks "Gang of Four", "GoF", "Pola Desain: Unsur-unsur Perangkat Lunak Berorientasi Objek Reusable ") .
Singkatnya, templat dapat berguna ketika Anda harus dapat melakukan tindakan apa pun dari jenis yang sama pada sekelompok objek dari jenis yang berbeda yang tidak terhubung satu sama lain. Atau, dengan kata lain, untuk memperluas fungsionalitas seri tipe ini dengan operasi tertentu dari tipe yang sama atau memiliki satu sumber. Pada saat yang sama, struktur dan implementasi tipe yang dapat diperluas tidak boleh terpengaruh.
Cara termudah untuk menjelaskan gagasan itu adalah dengan sebuah contoh.

Saya segera ingin membuat reservasi bahwa contohnya adalah fiksi dan disusun untuk tujuan akademis. Yaitu bahan ini dimaksudkan untuk memperkenalkan penerimaan OOP, dan tidak membahas masalah yang sangat khusus.

Saya juga ingin menarik perhatian pada fakta bahwa kode dalam contoh ditulis untuk mempelajari teknik desain. Saya menyadari kekurangannya (kode) dan kemungkinan memperbaikinya untuk digunakan dalam proyek nyata.

Contoh


Misalkan Anda memiliki subtipe UITableViewController yang menggunakan beberapa subtipe UITableViewCell :

 class FirstCell: UITableViewCell { /**/ } class SecondCell: UITableViewCell { /**/ } class ThirdCell: UITableViewCell { /**/ } class TableVC: UITableViewController { override func viewDidLoad() { super.viewDidLoad() tableView.register(FirstCell.self, forCellReuseIdentifier: "FirstCell") tableView.register(SecondCell.self, forCellReuseIdentifier: "SecondCell") tableView.register(ThirdCell.self, forCellReuseIdentifier: "ThirdCell") } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { /**/ return FirstCell() /**/ return SecondCell() /**/ return ThirdCell() } } 

Misalkan sel-sel subtipe yang berbeda memiliki ketinggian yang berbeda.

Tentu saja, perhitungan tinggi dapat ditempatkan langsung dalam implementasi setiap jenis sel. Tetapi bagaimana jika tinggi sel tidak hanya tergantung pada jenisnya sendiri, tetapi juga pada kondisi eksternal? Misalnya, tipe sel dapat digunakan di tabel yang berbeda dengan ketinggian yang berbeda. Dalam hal ini, kami benar-benar tidak ingin subclass UITableViewCell menyadari kebutuhan “superview” atau “view controller” mereka.

Kemudian perhitungan tinggi dapat dilakukan dalam metode UITableViewController : menginisialisasi UITableViewCell dengan nilai tinggi, atau UITableViewCell contoh UITableViewCell ke subtipe tertentu dan mengembalikan nilai yang berbeda dalam metode tableView(_:heightForRowAt:) . Tetapi pendekatan ini juga bisa menjadi tidak fleksibel dan berubah menjadi urutan panjang operator "jika" atau konstruksi "sakelar" yang besar.

Memecahkan masalah menggunakan templat "Pengunjung"


Tentu saja, tidak hanya template "Pengunjung" yang mampu menyelesaikan masalah ini, tetapi ia juga dapat melakukannya dengan sangat elegan.

Untuk melakukan ini, pertama, kita akan membuat tipe yang, pada kenyataannya, akan menjadi "pengunjung" dari tipe sel dan objek yang tanggung jawabnya hanya untuk menghitung tinggi sel tabel:

 struct HeightResultVisitor { func visit(_ ell: FirstCell) -> CGFloat { return 10.0 } func visit(_ ell: SecondCell) -> CGFloat { return 20.0 } func visit(_ ell: ThirdCell) -> CGFloat { return 30.0 } } 

Jenis ini menyadari setiap subtipe yang digunakan dan mengembalikan nilai yang diinginkan untuk masing-masing subtipe.

Kedua, setiap subtipe dari UITableViewCell harus dapat "menerima" pengunjung "ini". Untuk melakukan ini, kami akan mendeklarasikan protokol dengan metode "penerima", yang akan diimplementasikan oleh semua tipe sel yang digunakan:

 protocol HeightResultVisitable { func accept(_ visitor: HeightResultVisitor) -> CGFloat } extension FirstCell: HeightResultVisitable { func accept(_ visitor: HeightResultVisitor) -> CGFloat { return visitor.visit(self) } } extension SecondCell: HeightResultVisitable { func accept(_ visitor: HeightResultVisitor) -> CGFloat { return visitor.visit(self) } } extension ThirdCell: HeightResultVisitable { func accept(_ visitor: HeightResultVisitor) -> CGFloat { return visitor.visit(self) } } 

Di dalam subkelas UITableViewController , fungsionalitas dapat digunakan sebagai berikut:

 override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let cell = tableView.cellForRow(at: indexPath) as! HeightResultVisitable return cell.accept(HeightResultVisitor()) } 

Bisa lebih baik!


Kemungkinan besar, kami tidak ingin memiliki kode seperti itu yang secara kaku melekat pada fungsi tertentu. Mungkin kita ingin dapat menambahkan fungsionalitas baru ke set sel kita, tetapi tidak hanya mengenai tingginya, tetapi, katakanlah, warna latar belakang, teks di dalam sel, dll, dan tidak terikat pada jenis nilai pengembalian. Protokol dengan jenis associatedtype ( "Protokol dengan Jenis Terkait", "PAT" ) akan membantu di sini:

 protocol CellVisitor { associatedtype T func visit(_ cell: FirstCell) -> T func visit(_ cell: SecondCell) -> T func visit(_ cell: ThirdCell) -> T } 

Implementasinya untuk mengembalikan tinggi sel:

 struct HeightResultCellVisitor: CellVisitor { func visit(_ cell: FirstCell) -> CGFloat { return 10.0 } func visit(_ cell: SecondCell) -> CGFloat { return 20.0 } func visit(_ cell: ThirdCell) -> CGFloat { return 30.0 } } 

Di sisi "tuan rumah", cukup hanya memiliki protokol umum dan satu-satunya implementasinya - untuk setiap "pengunjung" jenis ini. Hanya pihak "pengunjung" yang akan mengetahui berbagai jenis nilai pengembalian.

Protokol untuk "pengunjung yang menerima" (dalam buku "GoF" sisi ini disebut "Elemen") dari jenisnya akan berbentuk:

 protocol Visitableell where Self: UITableViewCell { func accept<V: CellVisitor>(_ visitor: V) -> VT } 

(Mungkin tidak ada batasan untuk tipe implementasi. Tetapi dalam contoh ini tidak masuk akal untuk mengimplementasikan protokol ini dengan subkelas dari UITableViewCell .)

Dan implementasinya dalam subtipe UITableViewCell :

 extension FirstCell: Visitableell { func accept<V: CellVisitor>(_ visitor: V) -> VT { return visitor.visit(self) } } extension SecondCell: Visitableell { func accept<V: CellVisitor>(_ visitor: V) -> VT { return visitor.visit(self) } } extension ThirdCell: Visitableell { func accept<V: CellVisitor>(_ visitor: V) -> VT { return visitor.visit(self) } } 

Dan akhirnya, gunakan:

 override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let cell = tableView.cellForRow(at: indexPath) as! Visitableell return cell.accept(HeightResultCellVisitor()) } 
Dengan demikian, kita dapat membuat, menggunakan berbagai implementasi dari "pengunjung", secara umum, hampir semua hal, dan tidak ada yang diperlukan dari "sisi penerima" untuk mendukung fungsi baru. Partai ini bahkan tidak akan menyadari apa yang sebenarnya telah diberikan oleh "tamu" itu.

Contoh lain


Mari kita coba mengubah warna latar sel menggunakan "pengunjung" yang serupa:

 struct ColorResultCellVisitor: CellVisitor { func visit(_ cell: FirstCell) -> UIColor { return .black } func visit(_ cell: SecondCell) -> UIColor { return .white } func visit(_ cell: ThirdCell) -> UIColor { return .red } } 

Contoh menggunakan pengunjung ini:

 override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { cell.contentView.backgroundColor = (cell as! Visitableell).accept(ColorResultCellVisitor()) } 

Sesuatu dalam kode ini harus membingungkan ... Pada awalnya, dikatakan bahwa "pengunjung" dapat menambahkan fungsionalitas ke kelas dari luar. Jadi apakah mungkin untuk "menyembunyikan" di dalamnya semua fungsi untuk mengubah warna latar belakang sel, dan tidak hanya mendapatkan nilai dari itu? Kamu bisa. Maka tipe associatedtype akan mengambil nilai Void (aka () - tuple kosong) :

 struct BackgroundColorSetter: CellVisitor{ func visit(_ cell: FirstCell) { cell.contentView.backgroundColor = .black } func visit(_ cell: SecondCell) { cell.contentView.backgroundColor = .white } func visit(_ cell: ThirdCell) { cell.contentView.backgroundColor = .red } } 

Penggunaan:

 override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { (cell as! Visitableell).accept(BackgroundColorSetter()) } 


Alih-alih sebuah kesimpulan



Anda mungkin menyukai polanya hampir pada pandangan pertama, namun Anda harus menggunakannya dengan hati-hati. Kemunculannya dalam kode seringkali dapat menjadi pertanda kelemahan yang lebih umum dalam arsitektur. Mungkin Anda sedang mencoba menghubungkan hal-hal yang seharusnya tidak terhubung. Mungkin fungsi yang ditambahkan layak menempatkan satu tingkat abstraksi lebih tinggi dalam satu atau lain cara.

Dengan satu atau lain cara, hampir semua pola memiliki kelebihan dan kekurangan, dan sebelum menggunakannya Anda harus selalu berpikir dan membuat keputusan secara sadar. Pola, di satu sisi, merupakan cara untuk menggeneralisasi teknik pemrograman agar lebih mudah dibaca dan diskusi kode. Dan di sisi lain - cara untuk memecahkan masalah (kadang-kadang diperkenalkan secara artifisial). Dan, tentu saja, dalam hal apa pun, jangan secara fanatik membawa kode ke semua pola yang diketahui hanya karena fakta penggunaannya.


Saya kira saya sudah selesai! Semua kode cantik dan lebih sedikit "bug"!

Artikel saya yang lain tentang pola desain:

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


All Articles