2016-07-20 14 views
22

Biorąc pod uwagę następujące klasy Kotlin:Przestawianie getter dla Kotlin klasy danych

data class Test(val value: Int) 

Jak bym zastąpić Int getter tak, że zwraca 0 jeżeli wartość ujemną?

Jeśli nie jest to możliwe, jakie są techniki pozwalające uzyskać odpowiedni wynik?

+9

Należy rozważyć zmianę struktury kodu tak, aby wartości ujemne były konwertowane na 0, gdy klasa jest instancjonowana, a nie w pobierającym. Jeśli zastąpisz gettera w sposób opisany w poniższej odpowiedzi, wszystkie inne generowane metody, takie jak equals(), toString() i dostęp do komponentów, będą nadal używać oryginalnej wartości ujemnej, co prawdopodobnie doprowadzi do zaskakującego zachowania. – yole

Odpowiedz

18

Po spędzeniu prawie pełny rok pisząc Kotlin codziennie Odkryłam, że robi to z klas danych jest zła praktyka. Są to 3 ważne podejścia do tego i po tym, jak wytłumaczę, dlaczego podejście, które sugerują inni ludzie, jest złe.

  1. Wyraź swoją logikę biznesową, która tworzy data class zmieniać wartość jako 0 lub większa przed wywołaniem konstruktora ze złych wartości. Jest to prawdopodobnie najlepsze podejście w większości przypadków.

  2. Nie należy używać . Użyj zwykłego class i niech Twoje IDE wygeneruje dla Ciebie metody equals i hashCode (lub nie, jeśli ich nie potrzebujesz). Tak, będziesz musiał go ponownie wygenerować, jeśli którakolwiek z właściwości zostanie zmieniona na obiekcie, ale pozostawisz całkowitą kontrolę nad obiektem.

    class Test(value: Int) { 
        val value: Int = value 
        get() = if (field < 0) 0 else field 
    
        override fun equals(other: Any?): Boolean { 
        if (this === other) return true 
        if (other !is Test) return false 
        return true 
        } 
    
        override fun hashCode(): Int { 
        return javaClass.hashCode() 
        } 
    } 
    
  3. utworzyć dodatkową bezpieczną nieruchomości na obiekt, który robi to, co chcesz zamiast prywatną wartości, która jest skutecznie nadpisane.

    data class Test(val value: Int) { 
        val safeValue: Int 
        get() = if (value < 0) 0 else value 
    } 
    

Złe podejście inne odpowiedzi sugeruje:

data class Test(private val _value: Int) { 
    val value: Int 
    get() = if (_value < 0) 0 else _value 
} 

Problem z tego podejścia jest to, że nie są naprawdę data classes przeznaczona do zmiany danych, takich jak ten. Są tak naprawdę tylko do przechowywania danych. Przesłonięcie gettera dla takiej klasy danych oznaczałoby, że Test(0) i Test(-1) nie będą miały siebie nawzajem equal i będą mieć różne hashCode s, ale po wywołaniu .value, będą miały taki sam wynik. Jest to niespójne i chociaż może działać dla ciebie, inne osoby w twoim zespole, które widzą, że jest to klasa danych, mogą przypadkowo nadużyć, nie zdając sobie sprawy, jak to zmieniłeś/sprawiłeś, że nie działała zgodnie z oczekiwaniami (tj. Takie podejście nie byłoby ". t działa poprawnie w Map lub Set).

22

można spróbować coś takiego:

data class Test(private val _value: Int) { 
    val value = _value 
    get(): Int { 
     return if (field < 0) 0 else field 
    } 
} 

assert(1 == Test(1).value) 
assert(0 == Test(0).value) 
assert(0 == Test(-1).value) 

assert(1 == Test(1)._value) // Fail because _value is private 
assert(0 == Test(0)._value) // Fail because _value is private 
assert(0 == Test(-1)._value) // Fail because _value is private 
  • W klasie danych musisz oznaczyć parametry pierwotnym konstruktorów z obu val lub var.

  • Przypisuję wartość _value do value, aby użyć żądanej nazwy nieruchomości.

  • Zdefiniowałem niestandardowy akcesor dla właściwości z opisaną logiką.

+0

To jest złe rozwiązanie, które wprowadza kilka problemów. W mojej odpowiedzi podałem kilka lepszych alternatyw wraz z wyjaśnieniem, dlaczego jest to złe podejście. – spierce7

-1

Można po prostu

data class Test(private val number: Int) { 
    var value = number 
     get(){ 
      return if (field < 0) 0 else field 
     } 
} 

Proszę odnieść się do Properties and Fields

+1

OP odnosi się do przesłaniania 'val value: Int' przy zachowaniu jej nazewnictwa. Jak już wspomniano, nie ma możliwości przesłonięcia programu pobierającego/ustawiającego parametru konstruktora klasy danych. Twoja odpowiedź jest również błędna, ponieważ nie deklarujesz swojego parametru konstruktora jako prywatnego, więc OP będzie kończył się dwoma polami, jeden zwracający liczbę ujemną, jeśli zostanie błędnie wywołany, a drugi zwróci zero, jeśli pierwsza jest ujemna ... – mradzinski

4

Odpowiedź zależy od możliwości jego używania, które data zapewnia. @EPadron wspomniano fajną sztuczkę (ulepszona wersja):

data class Test(private val _value: Int) { 
    val value: Int 
     get() = if (_value < 0) 0 else _value 
} 

To będzie działa zgodnie z oczekiwaniami, e.i ma jeden pola, jeden getter, prawy equals, hashcode i component1. Połów jest, że toString i copy są dziwne:

println(Test(1))   // prints: Test(_value=1) 
Test(1).copy(_value = 5) // <- weird naming 

Aby rozwiązać ten problem z toString można zdefiniować ją za ręce. Nie wiem, w jaki sposób naprawić nazewnictwo parametrów, ale w ogóle nie korzystać z data.

-1

To wydaje się być jedną (między innymi) denerwujące wady Kotlin.

Wydaje się, że jedynym rozsądnym rozwiązaniem, które całkowicie zachowuje kompatybilność wstecz klasy, jest przekształcenie jej w zwykłą klasę (nie jako "klasy danych") i wdrożenie ręcznie (za pomocą IDE) metody: hashCode() jest równy() toString(), skopiuj() i() componentN

class Data3(i: Int) 
{ 
    var i: Int = i 

    override fun equals(other: Any?): Boolean 
    { 
     if (this === other) return true 
     if (other?.javaClass != javaClass) return false 

     other as Data3 

     if (i != other.i) return false 

     return true 
    } 

    override fun hashCode(): Int 
    { 
     return i 
    } 

    override fun toString(): String 
    { 
     return "Data3(i=$i)" 
    } 

    fun component1():Int = i 

    fun copy(i: Int = this.i): Data3 
    { 
     return Data3(i) 
    } 

} 
+0

Nie pewnie nazwałbym to wadą. Jest to jedynie ograniczenie funkcji klasy danych, która nie jest funkcją oferowaną przez Javę. – spierce7

-1

wiem, że to jest stare pytanie, ale wydaje się, nikt nie wspomniał o możliwości, aby wartość prywatne i pisanie na zlecenie getter tak:

data class Test(private val value: Int) { 
    fun getValue(): Int = if (value < 0) 0 else value 
} 

ta powinna być całkowicie poprawny jak Kotlin nie wygeneruje domyślny getter dla prywatnych pole.

Ale poza tym zdecydowanie zgadzam się ze spierce7, że klasy danych służą do przechowywania danych, dlatego należy unikać logiki "business" opartej na hardcoding.

+0

Oto co kilka osób sugeruje powyżej. Jest to złe podejście z powodów, które opisuję na dole mojej odpowiedzi powyżej. – spierce7

Powiązane problemy