Cara praktis untuk memetakan data di Kotlin

Pemetaan data adalah salah satu cara untuk memisahkan kode aplikasi menjadi beberapa lapisan. Pemetaan banyak digunakan dalam aplikasi Android. Contoh populer dari arsitektur aplikasi mobile Android-CleanArchitecture menggunakan pemetaan baik dalam versi asli ( contoh mapper dari CleanArchitecture ) dan di versi Kotlin baru ( contoh mapper ).


Pemetaan memungkinkan Anda untuk membuka lapisan-lapisan aplikasi (misalnya, menyingkirkan API), menyederhanakan dan membuat kode lebih visual.


Contoh pemetaan bermanfaat ditunjukkan pada diagram:



Tidak perlu untuk mentransfer semua bidang model Person jika di bagian aplikasi yang menarik bagi kita, kita hanya perlu dua bidang: login dan password . Jika lebih nyaman bagi kita untuk menganggap Person sebagai pengguna aplikasi, setelah peta kita dapat dengan mudah menggunakan model dengan nama yang kita pahami.


Mari kita pertimbangkan metode pemetaan data yang nyaman dan praktis dengan menggunakan contoh konversi dua model Person dan Salary dari layer Source ke model layer Destination .



Sebagai contoh, model disederhanakan. Person berisi Salary di kedua lapisan aplikasi.


Dalam kode ini, jika Anda memiliki model yang sama, mungkin ada baiknya merevisi lapisan aplikasi dan tidak menggunakan pemetaan.


Metode # 1: Metode Mapper


Contoh:


 class PersonSrc( private val name: String, private val salary: SalarySrc ) { fun mapToDestination() = PersonDst( name, salary.mapToDestination() //    Salary ) } class SalarySrc( private val amount: Int ) { fun mapToDestination() = SalaryDst(amount) } 

Metode tercepat dan termudah. Dialah yang digunakan di CleanArchitecture Kotlin ( contoh pemetaan ).


Nilai plus adalah kemampuan untuk menyembunyikan bidang. Bidang di PersonSrc bisa bersifat private , kode yang menggunakan kelas PersonSrc tidak tergantung pada mereka, yang berarti bahwa koherensi kode berkurang.


Kode semacam itu lebih cepat ditulis dan lebih mudah dimodifikasi - deklarasi lapangan dan penggunaannya ada di satu tempat. Tidak perlu menjalankan proyek dan memodifikasi file yang berbeda saat mengubah bidang kelas.


Namun, opsi ini lebih sulit untuk diuji. Metode mapper dari kelas PersonSrc PersonSrc panggilan ke metode mapper, SalarySrc . Jadi menguji hanya pemetaan Person tanpa pemetaan Salary akan lebih sulit. Anda harus menggunakan moki untuk ini.


Masalah lain mungkin muncul jika, sesuai dengan persyaratan arsitektur, lapisan aplikasi tidak dapat saling mengetahui: i.e. dalam kelas lapisan Src , Anda tidak dapat bekerja dengan lapisan Dst dan sebaliknya. Dalam hal ini, versi pemetaan ini tidak dapat digunakan.


Dalam contoh yang dipertimbangkan, lapisan Src bergantung pada lapisan Dst dan dapat membuat kelas-kelas dari lapisan ini. Untuk situasi yang berlawanan (ketika Dst tergantung pada Src ), opsi dengan metode pabrik statis cocok:


 class PersonDst( private val name: String, private val salary: SalaryDst ) { companion object { fun fromSource( src: PersonSrc ) = PersonDst(src.name, SalaryDst.fromSource(src.salary)) } } class SalaryDst( private val amount: Int ) { companion object { fun fromSource(src: SalarySrc) = SalaryDst(src.amount) } } 

Pemetaan ada di dalam kelas-kelas lapisan Dst , yang berarti kelas-kelas ini tidak mengungkapkan semua properti dan strukturnya ke kode yang menggunakannya.


Jika dalam aplikasi satu lapisan bergantung pada yang lain dan data ditransfer antara lapisan aplikasi di kedua arah, logis untuk menggunakan metode pabrik statis bersama dengan metode mapper.


Ringkasan Metode Pemetaan:


+ Cepat menulis kode, pemetaan selalu ada
+ Modifikasi mudah
+ Konektivitas kode rendah
- Pengujian Unit Sulit (diperlukan moki)
- Tidak selalu diizinkan oleh arsitektur


Metode 2: Fungsi Mapper


Model:


 class PersonSrc( val name: String, val salary: SalarySrc ) class SalarySrc(val amount: Int) class PersonDst( val name: String, val salary: SalaryDst ) class SalaryDst(val amount: Int) 

Pemeta:


 fun mapPerson( src: PersonSrc, salaryMapper: (SalarySrc) -> SalaryDst = ::mapSalary //  - ) = PersonDst( src.name, salaryMapper.invoke(src.salary) ) fun mapSalary(src: SalarySrc) = SalaryDst(src.amount) 

Dalam contoh ini, mapPerson adalah fungsi urutan yang lebih tinggi sejak itu dia mendapat mapper untuk model Salary . Fitur menarik dari contoh spesifik adalah argumen default untuk fungsi ini. Pendekatan ini memungkinkan kita untuk menyederhanakan kode panggilan dan pada saat yang sama dengan mudah mendefinisikan kembali mapper dalam unit test. Anda dapat menggunakan metode pemetaan ini tanpa metode default, meneruskannya selalu dalam kode panggilan.


Menempatkan mapper dan kelas yang digunakannya di berbagai tempat proyek tidak selalu nyaman. Dengan modifikasi kelas yang sering, Anda harus mencari dan memodifikasi file yang berbeda di tempat yang berbeda.


Metode pemetaan ini mengharuskan semua properti dengan data kelas dapat dilihat oleh mapper, yaitu visibilitas private tidak dapat digunakan untuk mereka.


Ringkasan Metode Pemetaan:


+ Pengujian Unit Sederhana
- Modifikasi yang sulit
- Membutuhkan bidang terbuka untuk kelas data


Metode 3: Fungsi Ekstensi


Pemeta:


 fun PersonSrc.toDestination( salaryMapper: (SalarySrc) -> SalaryDst = SalarySrc::toDestination ): PersonDst { return PersonDst(this.name, salaryMapper.invoke(this.salary)) } fun SalarySrc.toDestination(): SalaryDst { return SalaryDst(this.amount) } 

Secara umum, sama seperti fungsi mapper, tetapi sintaks dari panggilan mapper lebih sederhana: .toDestination() .


Perlu dicatat bahwa fungsi ekstensi dapat menyebabkan perilaku yang tidak terduga karena sifatnya yang statis: https://kotlinlang.org/docs/reference/extensions.html#extensions-are-resolved-statically


Ringkasan Metode Pemetaan:


+ Pengujian Unit Sederhana
- Modifikasi yang sulit
- Membutuhkan bidang terbuka untuk kelas data


Metode 4: Kelas Mapper dengan Antarmuka


Contoh fungsi memiliki kelemahan. Mereka memungkinkan Anda untuk menggunakan fungsi apa pun dengan tanda tangan (SalarySrc) -> SalaryDst . Kehadiran antarmuka Mapper<SRC, DST> akan membantu membuat kode lebih jelas.


Contoh:


 interface Mapper<SRC, DST> { fun transform(data: SRC): DST } class PersonMapper( private val salaryMapper: Mapper<SalarySrc, SalaryDst> ) : Mapper<PersonSrc, PersonDst> { override fun transform(src: PersonSrc) = PersonDst( src.name, salaryMapper.transform(src.salary) ) } class SalaryMapper : Mapper<SalarySrc, SalaryDst> { override fun transform(src: SalarrSrc) = SalaryDst( src.amount ) } 

Dalam contoh ini, SalaryMapper adalah ketergantungan PersonMapper . Ini memungkinkan Anda mengganti mapper Salary untuk unit test.


Mengenai pemetaan dalam fungsi, contoh ini hanya memiliki satu kelemahan - kebutuhan untuk menulis kode lebih sedikit.


Ringkasan Metode Pemetaan:


+ Mengetik lebih baik
- Lebih banyak kode


Seperti fungsi mapper:


+ Pengujian Unit Sederhana
- Modifikasi yang sulit
- Membutuhkan bidang terbuka untuk kelas data


Metode 5: Refleksi


Metode ilmu hitam. Pertimbangkan metode ini pada model lain.


Model:


 data class EmployeeSrc( val firstName: String, val lastName: String, val age: Int //    ) data class EmployeeDst( val name: String, //  ,    val age: Int //    ) 

Mapper:


 fun EmployeeSrc.mapWithRef() = with(::EmployeeDst) { val propertiesByName = EmployeeSrc::class.memberProperties.associateBy { it.name } callBy(parameters.associateWith { parameter -> when (parameter.name) { EmployeeDst::name.name -> "$firstName $lastName" //    name else -> propertiesByName[parameter.name]?.get(this@mapWithRef) //     } }) } 

Contoh dimata-matai di sini .


Dalam contoh ini, EmployeeSrc dan EmployeeDst menyimpan nama dalam format yang berbeda. Mapper hanya perlu membuat nama untuk model baru. Kolom yang tersisa diproses secara otomatis, tanpa menulis kode (pilihan else adalah when ).


Metode ini dapat berguna, misalnya, jika Anda memiliki model besar dengan sekelompok bidang dan bidang pada dasarnya bertepatan untuk model yang sama dari lapisan yang berbeda.


Masalah besar akan muncul, misalnya, jika Anda menambahkan bidang yang diperlukan ke Dst dan itu tidak terjadi di Src atau di mapper secara tidak sengaja: IllegalArgumentException dalam runtime. Refleksi juga memiliki masalah kinerja.


Ringkasan Metode Pemetaan:


+ lebih sedikit kode
+ pengujian unit sederhana
- berbahaya
- dapat mempengaruhi kinerja


Kesimpulan


Kesimpulan semacam itu dapat diambil dari pertimbangan kami:


Metode Mapper - kode yang jelas, lebih cepat untuk menulis dan memelihara


Fungsi Mapper dan fungsi ekstensi - hanya menguji pemetaan.


Kelas Mapper dengan antarmuka - hanya menguji pemetaan dan kode yang lebih jelas.


Refleksi - cocok untuk situasi yang tidak standar.

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


All Articles