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()
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
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
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"
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.