2016-08-29 10 views
22

Chcę przekonwertować/odwzorować niektóre obiekty klasy "dane" na podobne obiekty klasy "danych". Na przykład klasy formularza internetowego do klas dla rekordów bazy danych.Lepszy sposób zamapowania obiektów danych Kotlin na obiekty danych

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 
) 

używam ModelMapper dla takich prac w Javie, ale nie może być stosowane, ponieważ klas danych są ostateczne (ModelMapper tworzy proxy CGLib czytać definicje mapowania). Możemy użyć ModelMapper gdy otworzymy te klasy/pola, ale musimy ręcznie zaimplementować funkcje klasy "data". (porównaj przykłady ModelMapper: https://github.com/jhalterman/modelmapper/blob/master/examples/src/main/java/org/modelmapper/gettingstarted/GettingStartedExample.java)

Jak zmapować takie obiekty "danych" w Kotlin?

Aktualizacja: ModelMapper automatycznie mapuje pola o tej samej nazwie (np. Tel -> tel) bez deklaracji odwzorowań. Chcę to zrobić z klasą danych Kotlin.

Aktualizacja: Cel każdej klasy zależy od rodzaju aplikacji, ale są one prawdopodobnie umieszczone w różnych warstwach aplikacji.

Na przykład:

  • dane z bazy danych (Entity Database) do danych formularza HTML (Model/Widok Model)
  • REST API wynik do danych dla bazy

Klasy te są podobne, ale nie są takie same.

chcę uniknąć normalne funkcjonowanie wymaga tych powodów:

  • To zależy od kolejności argumentów. Funkcja dla klasy z wieloma polami, które mają ten sam typ (np. String), będzie łatwo zepsuta.
  • Wiele deklaracji jest obowiązkowych, chociaż większość odwzorowań można rozwiązać za pomocą konwencji nazewnictwa.

Oczywiście biblioteka o podobnej funkcji jest przeznaczona, ale informacje o funkcji Kotlin również są mile widziane (jak rozpowszechnianie w ECMAScript).

+0

Proszę opisać, w jaki sposób mają być używane odwzorowane klasy. Jaki jest cel posiadania dwóch oddzielnych formatów danych? – voddan

+0

Nigdy nie słyszałem o powielaniu modelu danych (z wyjątkiem przypadków starego kodu). Zwykle dane, z którymi pracujesz (View Model) ** to ** dane, które umieszczasz w bazie danych. – voddan

+0

Jednym z przypadków użycia @voddana byłoby ujawnienie tylko części modelu domeny różnym użytkownikom API. Posiadanie oddzielnego DTO na _view_ modelu domeny jest znacznie czystsze niż użycie np. [JsonView] (http://fasterxml.github.io/jackson-annotations/javadoc/2.1.1/com/fasterxml/jackson/annotation/JsonView.html) IMHO – miensol

Odpowiedz

9

Czy tego szukasz?

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)   
    } 
} 

, a następnie:

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

Operacja, którą piszesz, chciałem zrobić. Ale chcę zmniejszyć deklaracje mapowania, ponieważ istnieje wiele pól o tych samych nazwach (np. Tel -> tel). Chcę pisać tylko specjalne zasady, takie jak firstName + lastName => name. – sunnyone

+0

Propozycja spreadów nazw/nazwanych może być przydatna w tym przypadku? https://youtrack.jetbrains.com/issue/KT-15471 –

3

Naprawdę chcesz oddzielną klasę dla tego? Możesz dodać właściwości do pierwotnego klasy danych:

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

Klasa spearate nie jest wymagana nesessarilly. Ale chcę uniknąć zależności od kolejności argumentów. – sunnyone

+1

@sunnyone Zawsze możesz użyć [nazwanych argumentów] (https://kotlinlang.org/docs/reference/functions.html#named-arguments) podczas konstruowania twoich obiektów danych, abyś nie polegał na kolejności, w której argumenty są określone w. – mfulton26

10
  1. Najprostszy (najlepszy?):

    fun PersonForm.toPersonRecord() = PersonRecord(
         name = "$firstName $lastName", 
         age = age, 
         tel = tel 
    ) 
    
  2. Odbicie (nie świetna wydajność):

    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. Cached odbicie (okay wydajność, ale nie tak szybko, jak # 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")}" 
    }) 
    
Powiązane problemy