Delegerede ejendomme i Kotlin

1. Introduktion

Kotlin-programmeringssproget har indfødt support til klasseegenskaber.

Egenskaber bakkes normalt direkte op af tilsvarende felter, men det behøver ikke altid at være sådan - så længe de er korrekt eksponeret for omverdenen, kan de stadig betragtes som egenskaber.

Dette kan opnås ved at håndtere dette i getters og settere eller ved at udnytte kraften i Delegater.

2. Hvad er delegerede egenskaber?

Kort sagt, delegerede egenskaber understøttes ikke af et klassefelt og delegerer at komme og indstille til et andet stykke kode. Dette muliggør, at delegeret funktionalitet kan trækkes ud og deles mellem flere lignende egenskaber - f.eks. lagring af egenskabsværdier på et kort i stedet for separate felter.

Delegerede ejendomme bruges ved at erklære ejendommen og den delegerede, den bruger. Det ved nøgleord angiver, at ejendommen styres af den angivne delegat i stedet for sit eget felt.

For eksempel:

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

Dette bruger det faktum, at en MutableMap er selv en delegeret, der giver dig mulighed for at behandle dens nøgler som egenskaber.

3. Standard delegerede egenskaber

Kotlin-standardbiblioteket leveres med et sæt standarddelegerede, der er klar til brug.

Vi har allerede set et eksempel på brug af en MutableMap til at bakke en ændret egenskab. På samme måde kan du sikkerhedskopiere en uforanderlig ejendom ved hjælp af en Kort - at give adgang til individuelle felter som egenskaber, men aldrig ændre dem.

Det doven delegeret tillader, at værdien af ​​en ejendom kun beregnes ved første adgang og derefter caches. Dette kan være nyttigt for egenskaber, der kan være dyre at beregne, og som du måske ikke nogensinde har brug for - for eksempel ved at blive indlæst fra en database:

klasse DatabaseBackedUser (userId: String) {val name: String by lazy {queryForValue ("SELECT name FROM users WHERE userId =: userId", mapOf ("userId" to userId)}

Det observerbar delegeret tillader, at en lambda udløses, når som helst værdien af ​​ejendommen ændres, for eksempel at tillade ændringsmeddelelser eller opdatering af andre relaterede egenskaber:

class ObservedProperty {var name: String by Delegates.observable ("") {prop, old, new -> println ("Old value: $ old, New value: $ new")}}

Fra og med Kotlin 1.4 er det også muligt at delegere direkte til en anden ejendom. For eksempel, hvis vi omdøber en ejendom i en API-klasse, kan vi lade den gamle være på plads og blot delegere til den nye:

klasse RenamedProperty {var newName: String = "" @Deprecated ("Brug nyt navn i stedet") var navn: Streng efter dette :: newName}

Her, når som helst vi får adgang til navn ejendom bruger vi effektivt nyt navn ejendom i stedet.

4. Oprettelse af dine delegerede

Der vil være tidspunkter, hvor du vil skrive dine delegerede i stedet for at bruge dem, der allerede findes. Dette er afhængig af at skrive en klasse, der udvider en af ​​to grænseflader - LæsOnlyProperty eller ReadWriteProperty.

Begge disse grænseflader definerer en metode, der kaldes getValue - som bruges til at levere den aktuelle værdi af den delegerede ejendom, når den læses. Dette tager to argumenter og returnerer ejendommens værdi:

  • thisRef - en henvisning til klassen, som ejendommen er i
  • ejendom - en refleksionsbeskrivelse af ejendommen, der delegeres

Det ReadWriteProperty interface definerer desuden en metode, der kaldes setValue der bruges til at opdatere den aktuelle værdi af ejendommen, når den skrives. Dette tager tre argumenter og har ingen returværdi:

  • thisRef - En henvisning til klassen, som ejendommen er i
  • ejendom - En refleksionsbeskrivelse af ejendommen, der delegeres
  • værdi - Den nye værdi af ejendommen

Fra og med Kotlin 1.4 blev ReadWriteProperty interface udvides faktisk LæsOnlyProperty. Dette giver os mulighed for at skrive en enkelt delegeret klasse implementering ReadWriteProperty og brug den til skrivebeskyttede felter i vores kode. Tidligere ville vi have været nødt til at skrive to forskellige delegerede - en til skrivebeskyttede felter og en anden til ændrede felter.

Lad os som et eksempel skrive en delegat, der altid arbejder med en databaseforbindelse i stedet for lokale felter:

klasse DatabaseDelegate (readQuery: String, writeQuery: String, id: Any): ReadWriteDelegate {fun getValue (thisRef: R, property: KProperty): T {return queryForValue (readQuery, mapOf ("id" to id))} fun setValue ( thisRef: R, egenskab: KProperty, værdi: T) {opdatering (writeQuery, mapOf ("id" til id, "værdi" til værdi))}

Dette afhænger af to funktioner på øverste niveau for at få adgang til databasen:

  • queryForValue - dette tager noget SQL, og nogle binder og returnerer den første værdi
  • opdatering - dette tager noget SQL, og nogle binder og behandler det som en UPDATE-sætning

Vi kan derefter bruge dette som enhver almindelig delegat og få vores klasse automatisk bakket op af databasen:

klasse DatabaseUser (userId: String) {var name: String by DatabaseDelegate ("SELECT name FROM users WHERE userId =: id", "UPDATE users SET name =: value WHERE userId =: id", userId) var email: String by DatabaseDelegate ("VÆLG e-mail fra brugere WHERE userId =: id", "UPDATE brugere SET E-mail =: værdi WHERE userId =: id", userId)}

5. Delegering af oprettelse af delegeret

En anden ny funktion, som vi har i Kotlin 1.4, er evnen til at delegere oprettelsen af ​​vores delegerede klasser til en anden klasse. Dette fungerer ved at implementere PropertyDelegateProvider interface, som har en enkelt metode til at instantiere noget, der skal bruges som den egentlige delegat.

Vi kan bruge dette til at udføre nogle koder omkring oprettelsen af ​​den delegerede, der skal bruges - for eksempel til at logge, hvad der sker. Vi kan også bruge det til dynamisk at vælge den delegerede, som vi skal bruge, baseret på den ejendom, den bruges til. For eksempel kan vi have en anden delegeret, hvis ejendommen er ugyldig:

klasse DatabaseDelegateProvider(readQuery: String, writeQuery: String, id: Any): PropertyDelegateProvider {tilsidesætte operator fun supplyDelegate (thisRef: T, prop: KProperty): ReadWriteDelegate {if (prop.returnType.isMarkedNullable) {return NullableDatabaseDelegate (readQuery, writeQuery, id)} else {return NonNullDatabaseDelegate (readQuery, writeQuery)

Dette giver os mulighed for at skrive enklere kode i hver delegat, fordi de kun skal fokusere på mere målrettede sager. I ovenstående ved vi det NonNullDatabaseDelegate vil kun nogensinde blive brugt på egenskaber, der ikke kan have en nul værdi, så vi har ikke brug for nogen ekstra logik for at håndtere det.

6. Resume

Ejendomsdelegering er en kraftfuld teknik, der giver dig mulighed for at skrive kode, der overtager kontrollen med andre egenskaber, og hjælper denne logik med at blive let delt mellem forskellige klasser. Dette muliggør robust, genanvendelig logik, der ser ud og føles som regelmæssig ejendomsadgang.

Et fuldt fungerende eksempel på denne artikel kan findes på GitHub.


$config[zx-auto] not found$config[zx-overlay] not found