2016-08-29 64 views
22

Tôi muốn chuyển đổi/ánh xạ một số đối tượng lớp "dữ liệu" thành các đối tượng lớp "dữ liệu" tương tự. Ví dụ, các lớp cho biểu mẫu web cho các lớp cho các bản ghi cơ sở dữ liệu.Cách tốt hơn để ánh xạ đối tượng dữ liệu Kotlin vào các đối tượng dữ liệu

data class PersonForm(
    val firstName: String, 
    val lastName: String, 
    val age: Int, 
    // maybe many fields exist here like address, card number, etc. 
    val tel: String 
) 
// maps to ... 
data class PersonRecord(
    val name: String, // "${firstName} ${lastName}" 
    val age: Int, // copy of age 
    // maybe many fields exist here like address, card number, etc. 
    val tel: String // copy of tel 
) 

tôi sử dụng ModelMapper cho các công trình như vậy trong Java, nhưng nó không thể được sử dụng bởi vì các lớp dữ liệu cuối cùng (ModelMapper tạo cglib proxy để đọc định nghĩa bản đồ). Chúng ta có thể sử dụng ModelMapper khi chúng ta mở các lớp/trường này, nhưng chúng ta phải thực hiện các tính năng của lớp "dữ liệu" theo cách thủ công. (xem ví dụ về ModelMapper: https://github.com/jhalterman/modelmapper/blob/master/examples/src/main/java/org/modelmapper/gettingstarted/GettingStartedExample.java)

Cách ánh xạ các đối tượng "dữ liệu" như vậy trong Kotlin?

Cập nhật: ModelMapper tự động ánh xạ các trường có cùng tên (như tel -> tel) mà không khai báo ánh xạ. Tôi muốn làm điều đó với lớp dữ liệu của Kotlin.

Cập nhật: Mục đích của mỗi lớp phụ thuộc vào loại ứng dụng, nhưng chúng có thể được đặt trong lớp khác của ứng dụng.

Ví dụ:

  • dữ liệu từ cơ sở dữ liệu (Database Entity) để dữ liệu cho dạng HTML (Model/Xem Model)
  • REST của kết quả API để dữ liệu cho cơ sở dữ liệu

Các lớp học này tương tự, nhưng không giống nhau.

Tôi muốn tránh chức năng bình thường đòi hỏi những lý do sau:

  • Nó phụ thuộc vào thứ tự của đối số. Một hàm cho một lớp có nhiều trường có cùng kiểu (như String) sẽ bị phá vỡ dễ dàng.
  • Nhiều tuyên bố không cần thiết mặc dù hầu hết các ánh xạ có thể được giải quyết bằng quy ước đặt tên.

Tất nhiên, thư viện có tính năng tương tự được dự định, nhưng thông tin của tính năng Kotlin cũng được chào đón (như phát tán trong ECMAScript).

+0

Vui lòng mô tả cách bạn muốn sử dụng các lớp được ánh xạ. Mục đích của việc có hai định dạng dữ liệu riêng biệt là gì? – voddan

+0

Không bao giờ nghe nói về sao chép mô hình dữ liệu (ngoại trừ trường hợp mã kế thừa). Thông thường, dữ liệu bạn đang làm việc với (Xem mô hình) ** là ** dữ liệu bạn đưa vào cơ sở dữ liệu. – voddan

+0

@voddan một trường hợp sử dụng sẽ chỉ hiển thị các phần của mô hình miền cho người tiêu dùng API khác nhau. Có DTO riêng biệt cho mỗi _view_ của mô hình miền sạch hơn nhiều so với sử dụng tức là [JsonView] (http://fasterxml.github.io/jackson-annotations/javadoc/2.1.1/com/fasterxml/jackson/annotation/JsonView.html) IMHO – miensol

Trả lời

9

Đây có phải là bạn đang tìm kiếm không?

data class PersonRecord(val name: String, val age: Int, val tel: String){  
    object ModelMapper { 
     fun from(form: PersonForm) = 
      PersonRecord(form.firstName + form.lastName, form.age, form.tel)   
    } 
} 

và sau đó:

val personRecord = PersonRecord.ModelMapper.from(personForm) 
+1

Các hoạt động mà bạn viết là tôi muốn làm. Nhưng tôi muốn giảm các khai báo ánh xạ vì nhiều trường có cùng tên (như tel -> tel) tồn tại. Tôi chỉ muốn viết các quy tắc đặc biệt như firstName + lastName => name. – sunnyone

+0

Đề xuất quy ước truyền dữ liệu/được đặt tên có thể hữu ích cho việc này? https://youtrack.jetbrains.com/issue/KT-15471 –

3

Bạn có thực sự muốn có một lớp riêng biệt cho điều đó? Bạn có thể thêm các thuộc tính cho lớp dữ liệu gốc:

data class PersonForm(
    val firstName: String, 
    val lastName: String, 
    val age: Int, 
    val tel: String 
) { 
    val name = "${firstName} ${lastName}" 
} 
+0

Lớp học spearate không bắt buộc phải là nesessarilly. Nhưng tôi muốn tránh tùy thuộc vào thứ tự của các đối số. – sunnyone

+1

@sunnyone Bạn luôn có thể sử dụng [đối số được đặt tên] (https://kotlinlang.org/docs/reference/functions.html#named-arguments) khi xây dựng các đối tượng dữ liệu của bạn để bạn không phụ thuộc vào thứ tự các đối số được định nghĩa trong. – mfulton26

10
  1. đơn giản nhất (tốt nhất?):

    fun PersonForm.toPersonRecord() = PersonRecord(
         name = "$firstName $lastName", 
         age = age, 
         tel = tel 
    ) 
    
  2. Reflection (không hiệu suất tuyệt vời):

    fun PersonForm.toPersonRecord() = with(PersonRecord::class.primaryConstructor!!) { 
        val propertiesByName = PersonForm::class.memberProperties.associateBy { it.name } 
        callBy(args = parameters.associate { parameter -> 
         parameter to when (parameter.name) { 
          "name" -> "$firstName $lastName" 
          else -> propertiesByName[parameter.name]?.get([email protected]) 
         } 
        }) 
    } 
    
  3. phản ánh Cached (hiệu suất ổn nhưng không nhanh như # 1):

    open class Transformer<in T : Any, out R : Any> 
    protected constructor(inClass: KClass<T>, outClass: KClass<R>) { 
        private val outConstructor = outClass.primaryConstructor!! 
        private val inPropertiesByName by lazy { 
         inClass.memberProperties.associateBy { it.name } 
        } 
    
        fun transform(data: T): R = with(outConstructor) { 
         callBy(parameters.associate { parameter -> 
          parameter to argFor(parameter, data) 
         }) 
        } 
    
        open fun argFor(parameter: KParameter, data: T): Any? { 
         return inPropertiesByName[parameter.name]?.get(data) 
        } 
    } 
    
    val personFormToPersonRecordTransformer = object 
    : Transformer<PersonForm, PersonRecord>(PersonForm::class, PersonRecord::class) { 
        override fun argFor(parameter: KParameter, data: PersonForm): Any? { 
         return when (parameter.name) { 
          "name" -> with(data) { "$firstName $lastName" } 
          else -> super.argFor(parameter, data) 
         } 
        } 
    } 
    
    fun PersonForm.toPersonRecord() = personFormToPersonRecordTransformer.transform(this) 
    
  4. Storing Properties in a Map

    data class PersonForm(val map: Map<String, Any?>) { 
        val firstName: String by map 
        val lastName: String by map 
        val age: Int   by map 
        // maybe many fields exist here like address, card number, etc. 
        val tel: String   by map 
    } 
    
    // maps to ... 
    data class PersonRecord(val map: Map<String, Any?>) { 
        val name: String by map // "${firstName} ${lastName}" 
        val age: Int  by map // copy of age 
        // maybe many fields exist here like address, card number, etc. 
        val tel: String  by map // copy of tel 
    } 
    
    fun PersonForm.toPersonRecord() = PersonRecord(HashMap(map).apply { 
        this["name"] = "${remove("firstName")} ${remove("lastName")}" 
    }) 
    
Các vấn đề liên quan