Bersihkan arsitektur cepat sebagai alternatif untuk VIPER

Pendahuluan


Saat ini, ada banyak artikel tentang VIPER - arsitektur bersih, berbagai variasi yang pada satu waktu menjadi populer untuk proyek iOS. Jika Anda tidak terbiasa dengan Viper, Anda dapat membacanya di sini , di sini atau di sini .

Saya ingin berbicara tentang alternatif VIPER - Clean Swift. Clean Swift pada pandangan pertama tampak seperti VIPER, namun, perbedaannya menjadi terlihat setelah mempelajari prinsip interaksi antar modul. Dalam VIPER, interaksi didasarkan pada Presenter, itu mentransfer permintaan pengguna ke Interactor untuk memproses dan memformat data yang diterima darinya kembali untuk ditampilkan pada View Controller:

gambar

Di Clean Swift, modul utama, seperti di VIPER, adalah View Controller, Interactor, Presenter.

gambar

Interaksi di antara mereka terjadi dalam siklus. Transfer data didasarkan pada protokol (sekali lagi, mirip dengan VIPER), yang memungkinkan untuk perubahan di masa depan di salah satu komponen sistem untuk hanya menggantinya dengan yang lain. Proses interaksi secara umum terlihat seperti ini: pengguna mengklik tombol, View Controller membuat objek dengan deskripsi dan mengirimkannya ke Interactor. Interactor, pada gilirannya, mengimplementasikan skenario spesifik sesuai dengan logika bisnis, menciptakan objek hasil dan meneruskannya ke Presenter. Presenter membentuk objek dengan data yang diformat untuk ditampilkan kepada pengguna dan mengirimkannya ke View Controller. Mari kita lihat lebih dekat setiap modul Clean Swift secara lebih rinci.

Lihat (Lihat Pengontrol)


View Controller, seperti pada VIPER, melakukan semua konfigurasi VIew, baik itu pengaturan warna, UILabel atau Layout font. Oleh karena itu, setiap UIViewController dalam arsitektur ini mengimplementasikan protokol input untuk menampilkan data atau menanggapi tindakan pengguna.

Interactractor


Interactor berisi semua logika bisnis. Ini menerima tindakan pengguna dari controller, dengan parameter (misalnya, teks yang berubah dari bidang input, menekan tombol) yang didefinisikan dalam protokol Input. Setelah mengerjakan logika, Interactor, jika perlu, harus mentransfer data untuk persiapannya ke Presenter sebelum menampilkannya di ViewController. Namun, Interactor hanya menerima permintaan dari View sebagai input, tidak seperti VIPER, di mana permintaan ini melalui Presenter.

Presenter


Presenter memproses data untuk ditampilkan kepada pengguna. Hasilnya dalam kasus ini adalah protokol Input ViewController's, di sini Anda dapat, misalnya, mengubah format teks, menerjemahkan nilai warna dari enum ke rgb, dll.

Pekerja


Agar tidak mempersulit Interactor dan tidak menggandakan detail logika bisnis, Anda dapat menggunakan elemen Pekerja tambahan. Dalam modul sederhana, itu tidak selalu diperlukan, tetapi dalam modul yang cukup dimuat memungkinkan Anda untuk menghapus beberapa tugas dari Interactor. Sebagai contoh, logika interaksi dengan database dapat dibuat pada pekerja, terutama jika query database yang sama dapat digunakan dalam modul yang berbeda.

Router


Router bertanggung jawab untuk mentransfer data ke modul lain dan transisi di antara mereka. Dia memiliki tautan ke pengontrol, karena di iOS, sayangnya, pengontrol, antara lain, secara historis bertanggung jawab atas transisi. Menggunakan segue dapat menyederhanakan inisialisasi transisi dengan memanggil metode Router dari Bersiap untuk segue, karena Router tahu cara mentransfer data, dan akan melakukannya tanpa kode loop tambahan dari Interactor / Presenter. Data ditransfer menggunakan protokol data warehouse dari setiap modul yang diimplementasikan dalam Interactor. Protokol-protokol ini juga membatasi kemampuan untuk mengakses data modul internal dari Router.

Model


Model adalah deskripsi struktur data untuk mentransfer data antar modul. Setiap implementasi fungsi logika bisnis memiliki deskripsi modelnya sendiri.

  • Permintaan - untuk mengirim permintaan dari controller ke interaktor.
  • Respons - respons interaksor untuk mengirimkan ke presenter dengan data.
  • ViewModel - untuk transfer data dalam bentuk yang siap ditampilkan di controller.

Contoh implementasi


Mari kita lihat lebih dekat arsitektur ini menggunakan contoh sederhana. Mereka akan dilayani oleh aplikasi ContactsBook secara sederhana, tetapi cukup memadai untuk memahami esensi dari bentuk arsitektur. Aplikasi ini mencakup daftar kontak, serta menambah dan mengedit kontak.

Contoh protokol input:

protocol ContactListDisplayLogic: class { func displayContacts(viewModel: ContactList.ShowContacts.ViewModel) } 

Setiap pengontrol berisi referensi ke objek yang mengimplementasikan protokol input Interactor

 var interactor: ContactListBusinessLogic? 

juga ke objek Router, yang harus menerapkan logika transfer data dan pengalihan modul:

 var router: (NSObjectProtocol & ContactListRoutingLogic & ContactListDataPassing)? 

Anda dapat menerapkan konfigurasi modul dalam metode pribadi yang terpisah:

 private func setup() { let viewController = self let interactor = ContactListInteractor() let presenter = ContactListPresenter() let router = ContactListRouter() viewController.interactor = interactor viewController.router = router interactor.presenter = presenter presenter.viewController = viewController router.viewController = viewController router.dataStore = interactor } 

atau buat singleton Configurator untuk menghapus kode ini dari controller (bagi mereka yang percaya bahwa controller tidak boleh terlibat dalam konfigurasi) dan tidak menggoda diri mereka sendiri dengan akses ke bagian-bagian modul di controller. Tidak ada kelas konfigurator dalam pandangan Paman Bob dan dalam VIPER klasik. Menggunakan konfigurator untuk modul add contact terlihat seperti ini:

 override func awakeFromNib() { super.awakeFromNib() AddContactConfigurator.sharedInstance.configure(self) } 

Kode konfigurator berisi satu-satunya metode konfigurasi yang benar-benar identik dengan metode pengaturan di controller:

 final class AddContactConfigurator { static let sharedInstance = AddContactConfigurator() private init() {} func configure(_ control: AddContactViewController) { let viewController = control let interactor = AddContactInteractor() let presenter = AddContactPresenter() let router = AddContactRouter() viewController.interactor = interactor viewController.router = router interactor.presenter = presenter presenter.viewController = viewController router.viewController = viewController router.dataStore = interactor } } 

Poin lain yang sangat penting dalam implementasi controller adalah kode dalam standar mempersiapkan metode segue:

 override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if let scene = segue.identifier { let selector = NSSelectorFromString("routeTo\(scene)WithSegue:") if let router = router, router.responds(to: selector) { router.perform(selector, with: segue) } } } 

Pembaca yang penuh perhatian kemungkinan besar memperhatikan bahwa Router juga diperlukan untuk mengimplementasikan NSObjectProtocol. Ini dilakukan agar kita dapat menggunakan metode standar protokol ini untuk perutean saat menggunakan segues. Untuk mendukung pengalihan sederhana ini, penamaan pengidentifikasi segue harus cocok dengan akhir nama metode Router. Misalnya, untuk melihat kontak, ada segue, yang terkait dengan pilihan sel dengan kontak. Pengenalnya adalah "ViewContact", berikut adalah metode yang sesuai di Router:

 func routeToViewContact(segue: UIStoryboardSegue?) 

Permintaan untuk menampilkan data ke Interactor juga terlihat sangat sederhana:

 private func fetchContacts() { let request = ContactList.ShowContacts.Request() interactor?.showContacts(request: request) } 

Mari kita beralih ke Interactor. Interactor mengimplementasikan protokol ContactListDataStore, yang bertanggung jawab untuk menyimpan / mengakses data. Dalam kasus kami, ini hanya sebuah array kontak, yang hanya dibatasi oleh metode pengambil, untuk menunjukkan kepada router bahwa tidak dapat diubahnya dari modul lain. Protokol yang menerapkan logika bisnis untuk daftar kami adalah sebagai berikut:

 func showContacts(request: ContactList.ShowContacts.Request) { let contacts = worker.getContacts() self.contacts = contacts let response = ContactList.ShowContacts.Response(contacts: contacts) presenter?.presentContacts(response: response) } 

Ini menerima data kontak dari ContactListWorker. Dalam hal ini, Pekerja bertanggung jawab atas bagaimana data diunduh. Ia dapat beralih ke layanan pihak ketiga yang memutuskan, misalnya, untuk mengambil data dari cache atau mengunduh dari jaringan. Setelah menerima data, Interactor mengirimkan Respons ke Presenter untuk mempersiapkan tampilan, karena Interactor ini berisi tautan ke Presenter:

 var presenter: ContactListPresentationLogic? 

Presenter mengimplementasikan hanya satu protokol - ContactListPresentationLogic, dalam kasus kami, itu hanya secara paksa mengubah kasus nama depan dan belakang kontak, membentuk model presentasi DisplayedContact dari model data dan meneruskannya ke Controller untuk ditampilkan:

 func presentContacts(response: ContactList.ShowContacts.Response) { let mapped = response.contacts.map { ContactList .ShowContacts .ViewModel .DisplayedContact(firstName: $0.firstName.uppercaseFirst, lastName: $0.lastName.uppercaseFirst) } let viewModel = ContactList.ShowContacts.ViewModel(displayedContacts: mapped) viewController?.displayContacts(viewModel: viewModel) } 

Setelah itu, siklus berakhir dan pengontrol menampilkan data, menerapkan metode protokol ContactListDisplayLogic:

 func displayContacts(viewModel: ContactList.ShowContacts.ViewModel) { displayedContacts = viewModel.displayedContacts tableView.reloadData() } 

Ini adalah bagaimana model untuk menampilkan kontak terlihat seperti:

 enum ShowContacts { struct Request { } struct Response { var contacts: [Contact] } struct ViewModel { struct DisplayedContact { let firstName: String let lastName: String var fullName: String { return firstName + " " + lastName } } var displayedContacts: [DisplayedContact] } } 

Dalam hal ini, permintaan tidak mengandung data, karena ini hanya daftar kontak umum, namun, jika, misalnya, layar daftar berisi filter, tipe filter dapat dimasukkan dalam permintaan ini. Model respons Intrecator berisi daftar kontak yang diinginkan, ViewModel juga berisi berbagai data yang siap untuk ditampilkan - DisplayedContact.

Mengapa Bersihkan Swift


Pertimbangkan pro dan kontra dari arsitektur ini. Pertama, Bersihkan Swift memiliki templat kode yang mempermudah pembuatan modul. Template ini dapat ditulis untuk banyak arsitektur, tetapi ketika mereka berada di luar kotak - setidaknya menghemat beberapa jam dari waktu Anda.

Kedua, arsitektur ini, seperti VIPER, telah diuji dengan baik, contoh-contoh tes tersedia di proyek. Karena modul tempat interaksi terjadi mudah untuk diganti dengan rintisan, menentukan fungsionalitas setiap modul menggunakan protokol memungkinkan Anda untuk mengimplementasikannya tanpa sakit kepala. Jika kita secara bersamaan membuat logika bisnis dan tes yang sesuai (Interactor, Interactor tests), ini cocok dengan prinsip TDD. Karena fakta bahwa output dan input dari setiap kasus logika didefinisikan oleh protokol, cukup hanya menulis tes pertama yang menentukan perilakunya, dan kemudian langsung menerapkan metode logika.

Ketiga, Clean Swift (tidak seperti VIPER) mengimplementasikan aliran searah pemrosesan data dan pengambilan keputusan. Hanya satu siklus yang selalu dijalankan - View - Interactor - Presenter - View, yang juga menyederhanakan refactoring, karena seringkali diperlukan untuk mengubah lebih sedikit entitas. Karena itu, proyek-proyek dengan logika yang sering berubah atau ditambah lebih mudah untuk refactor menggunakan metodologi Clean Swift. Menggunakan Clean Swift, Anda memisahkan entitas dengan dua cara:

  1. Mengisolasi komponen dengan mendeklarasikan protokol Input dan Output
  2. Mengisolasi fitur dengan menggunakan struktur dan merangkum data dalam permintaan / tanggapan / model UI yang terpisah. Setiap fitur memiliki logikanya sendiri dan dikendalikan dalam kerangka satu proses, tanpa berpotongan dalam satu modul dengan fitur lainnya.

Clean Swift tidak boleh digunakan dalam proyek kecil tanpa perspektif jangka panjang, dalam proyek prototipe. Misalnya, terlalu mahal untuk mengimplementasikan aplikasi untuk jadwal konferensi pengembang menggunakan arsitektur ini. Proyek jangka panjang, proyek dengan banyak logika bisnis, sebaliknya, sangat cocok dengan kerangka arsitektur ini. Sangat mudah untuk menggunakan Clean Swift ketika proyek diimplementasikan untuk dua platform - Mac OS dan iOS, atau direncanakan untuk port di masa depan.

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


All Articles