Gedelegeerde eigendommen in Kotlin

1. Inleiding

De Kotlin-programmeertaal heeft native ondersteuning voor klasse-eigenschappen.

Eigenschappen worden meestal direct ondersteund door overeenkomstige velden, maar het hoeft niet altijd zo te zijn - zolang ze correct worden blootgesteld aan de buitenwereld, kunnen ze nog steeds als eigenschappen worden beschouwd.

Dit kan worden bereikt door hiermee om te gaan in getters en setters, of door gebruik te maken van de kracht van Afgevaardigden.

2. Wat zijn gedelegeerde eigendommen?

Simpel gezegd, gedelegeerde eigenschappen worden niet ondersteund door een klasseveld en delegeren het ophalen en instellen naar een ander stuk code. Hierdoor kan gedelegeerde functionaliteit worden geabstraheerd en gedeeld tussen meerdere vergelijkbare eigenschappen - bijv. het opslaan van eigenschapswaarden op een kaart in plaats van in afzonderlijke velden.

Gedelegeerde eigenschappen worden gebruikt door de eigenschap en de gedelegeerde die deze gebruikt, te declareren. De door trefwoord geeft aan dat de eigenschap wordt beheerd door de opgegeven gemachtigde in plaats van door zijn eigen veld.

Bijvoorbeeld:

class DelegateExample (map: MutableMap) {var name: String by map}

Dit maakt gebruik van het feit dat een MutableMap is zelf een afgevaardigde, waardoor u de sleutels ervan als eigenschappen kunt behandelen.

3. Standaard gedelegeerde eigenschappen

De standaardbibliotheek van Kotlin wordt geleverd met een set standaardafgevaardigden die klaar zijn voor gebruik.

We hebben al een voorbeeld gezien van het gebruik van een MutableMap om een ​​veranderlijke eigenschap te ondersteunen. Op dezelfde manier kunt u een onveranderlijke eigenschap ondersteunen met een Kaart - toestaan ​​dat individuele velden worden geopend als eigenschappen, maar deze nooit wijzigen.

De lui delegate staat toe dat de waarde van een eigenschap alleen bij de eerste toegang wordt berekend en vervolgens in de cache wordt opgeslagen. Dit kan handig zijn voor eigenschappen die mogelijk duur zijn om te berekenen en die u misschien nooit nodig zult hebben, bijvoorbeeld om uit een database te worden geladen:

class DatabaseBackedUser (userId: String) {val name: String by lazy {queryForValue ("SELECT naam VAN gebruikers WHERE userId =: userId", mapOf ("userId" to userId)}}

De waarneembaar delegate staat toe dat een lambda wordt geactiveerd wanneer de waarde van de eigenschap verandert, bijvoorbeeld het toestaan ​​van wijzigingsmeldingen of het bijwerken van andere gerelateerde eigenschappen:

class ObservedProperty {var name: String door Delegates.observable ("") {prop, oud, nieuw -> println ("Oude waarde: $ oud, Nieuwe waarde: $ nieuw")}}

Vanaf Kotlin 1.4 is het ook mogelijk om rechtstreeks naar een ander pand te delegeren. Als we bijvoorbeeld een eigenschap in een API-klasse hernoemen, kunnen we de oude op zijn plaats laten en eenvoudig delegeren naar de nieuwe:

class RenamedProperty {var newName: String = "" @Deprecated ("Gebruik in plaats daarvan newName") var name: String by this :: newName}

Hier, elke keer dat we toegang hebben tot het naam eigendom, gebruiken we effectief de nieuwe naam eigendom.

4. Uw afgevaardigden maken

Er zullen momenten zijn dat u uw afgevaardigden wilt schrijven in plaats van reeds bestaande afgevaardigden te gebruiken. Dit is afhankelijk van het schrijven van een klasse die een van de twee interfaces uitbreidt: ReadOnlyProperty of ReadWriteProperty.

Beide interfaces definiëren een methode met de naam getValue - die wordt gebruikt om de huidige waarde van de gedelegeerde eigenschap op te geven wanneer deze wordt gelezen. Hiervoor zijn twee argumenten nodig en retourneert de waarde van de eigenschap:

  • thisRef - een verwijzing naar de klasse waarin het onroerend goed zich bevindt
  • eigendom - een reflectiebeschrijving van het eigendom dat wordt gedelegeerd

De ReadWriteProperty interface definieert bovendien een methode genaamd setValue dat wordt gebruikt om de huidige waarde van de eigenschap bij te werken wanneer deze wordt geschreven. Dit vereist drie argumenten en heeft geen retourwaarde:

  • thisRef - Een verwijzing naar de klasse waarin de woning zich bevindt
  • eigendom - Een reflectiebeschrijving van het eigendom dat wordt gedelegeerd
  • waarde - De nieuwwaarde van het onroerend goed

Vanaf Kotlin 1.4 is de ReadWriteProperty interface breidt zich eigenlijk uit ReadOnlyProperty. Dit stelt ons in staat om een ​​enkele gedelegeerde klasse te implementeren ReadWriteProperty en gebruik het voor alleen-lezen velden binnen onze code. Voorheen moesten we twee verschillende gedelegeerden schrijven: een voor alleen-lezen velden en een andere voor veranderlijke velden.

Laten we als voorbeeld een afgevaardigde schrijven die altijd werkt met betrekking tot een databaseverbinding in plaats van lokale velden:

class DatabaseDelegate (readQuery: String, writeQuery: String, id: Any): ReadWriteDelegate {fun getValue (thisRef: R, property: KProperty): T {return queryForValue (readQuery, mapOf ("id" naar id))} fun setValue ( thisRef: R, eigenschap: KProperty, waarde: T) {update (writeQuery, mapOf ("id" naar id, "waarde" naar waarde))}}

Dit hangt af van twee functies op het hoogste niveau om toegang te krijgen tot de database:

  • queryForValue - dit vereist wat SQL en wat bindt en retourneert de eerste waarde
  • bijwerken - dit vereist wat SQL en sommige bindt het en behandelt het als een UPDATE-instructie

We kunnen dit dan gebruiken zoals elke gewone afgevaardigde en onze klas automatisch laten ondersteunen door de database:

class DatabaseUser (userId: String) {var name: String by DatabaseDelegate ("SELECT naam VAN gebruikers WHERE userId =: id", "UPDATE gebruikers SET naam =: waarde WHERE userId =: id", userId) var email: String by DatabaseDelegate ("SELECTEER e-mail VAN gebruikers WHERE userId =: id", "UPDATE gebruikers SET email =: waarde WHERE userId =: id", userId)}

5. Het aanmaken van een gedelegeerde delegeren

Een andere nieuwe functie die we hebben in Kotlin 1.4 is de mogelijkheid om het aanmaken van onze deelnemersklassen aan een andere klas te delegeren. Dit werkt door het implementeren van de PropertyDelegateProvider interface, die een enkele methode heeft om iets te instantiëren dat als de daadwerkelijke gedelegeerde kan worden gebruikt.

We kunnen dit gebruiken om wat code uit te voeren rond het maken van de gedelegeerde om te gebruiken, bijvoorbeeld om te loggen wat er gebeurt. We kunnen het ook gebruiken om dynamisch de gedelegeerde te selecteren die we gaan gebruiken op basis van de eigenschap waarvoor het wordt gebruikt. We kunnen bijvoorbeeld een andere gemachtigde hebben als de eigenschap nullabel is:

class DatabaseDelegateProvider(readQuery: String, writeQuery: String, id: Any): PropertyDelegateProvider {override operator plezier biedenDelegate (thisRef: T, prop: KProperty): ReadWriteDelegate {if (prop.returnType.isMarkedNullable) {return NullableDatabaseDelegate (readQuery, writeQuery, id)} else {return NonNullDatabaseQuery,}

Dit stelt ons in staat om eenvoudigere code in elke afgevaardigde te schrijven, omdat ze zich alleen hoeven te concentreren op meer gerichte gevallen. In het bovenstaande weten we dat NonNullDatabaseDelegate wordt alleen gebruikt op eigenschappen die geen nul waarde, dus we hebben geen extra logica nodig om daarmee om te gaan.

6. Samenvatting

Eigendomsdelegatie is een krachtige techniek waarmee u code kunt schrijven die de controle over andere eigenschappen overneemt, en helpt deze logica gemakkelijk te delen tussen verschillende klassen. Dit zorgt voor robuuste, herbruikbare logica die eruitziet en aanvoelt als normale toegang tot eigendommen.

Een volledig werkend voorbeeld voor dit artikel is te vinden op GitHub.