Vejledning til Kotlin-grænseflader

1. Oversigt

I denne vejledning vi diskuterer, hvordan man definerer og implementerer grænseflader i Kotlin.

Vi ser også på, hvordan flere grænseflader kan implementeres af en klasse. Dette kan helt sikkert forårsage konflikter, og vi lærer den mekanisme, som Kotlin har til at løse dem.

2. Grænseflader i Kotlin

En grænseflade er en måde at give en beskrivelse eller kontrakt til klasser i objektorienteret programmering. De kan indeholde egenskaber og funktioner på abstrakte eller konkrete måder afhængigt af programmeringssprog. Vi gennemgår detaljerne i grænseflader i Kotlin.

Grænseflader i Kotlin svarer til grænseflader på mange andre sprog som Java. Men de har specifik syntaks, lad os gennemgå dem i de næste par underafsnit.

2.1. Definition af grænseflader

Lad os begynde med at definere vores første grænseflade i Kotlin:

interface SimpleInterface

Dette er den enkleste grænseflade, der er helt tom. Disse er også kendt som markørgrænseflader.

Lad os nu tilføje nogle funktioner til vores interface:

interface SimpleInterface {fun firstMethod (): String fun secondMethod (): String {return ("Hello, World!")}}

Vi har tilføjet to metoder til vores tidligere definerede interface:

  • En af dem kaldte firstMethod er en abstrakt metode
  • Mens den anden ringede til secondMethod har en standardimplementering.

Lad os fortsætte og tilføje nogle egenskaber til vores grænseflade nu:

interface SimpleInterface {val firstProp: String val secondProp: String get () = "Second Property" fun firstMethod (): String fun secondMethod (): String {return ("Hello, from:" + secondProp)}}

Her har vi tilføjet to egenskaber til vores interface:

  • En af dem ringede firstProp er af typen String og er abstrakt
  • Den anden ringede secondProp er også af typen streng, men den definerer en implementering for dens accessor.

Noter det egenskaber i en grænseflade kan ikke opretholde tilstand. Så følgende er et ulovligt udtryk i Kotlin:

interface SimpleInterface {val firstProp: String = "First Property" // Ulovlig erklæring}

2.2. Implementering af grænseflader

Nu hvor vi har defineret en grundlæggende grænseflade, lad os se, hvordan vi kan implementere det i en klasse i Kotlin:

klasse SimpleClass: SimpleInterface {override val firstProp: String = "First Property" tilsidesætter fun firstMethod (): String {return ("Hello, from:" + firstProp)}}

Bemærk, at når vi definerer SimpleClass som en implementering af SimpleInterface, Vi skal kun levere implementeringen af ​​abstrakte egenskaber og funktioner. Men vi kan også tilsidesætte enhver tidligere defineret egenskab eller funktion.

Lad os nu tilsidesætte alle tidligere definerede egenskaber og funktioner i vores klasse:

class SimpleClass: SimpleInterface {override val firstProp: String = "First Property" tilsidesættelse val secondProp: String get () = "Second Property, Overridden!" tilsidesætte sjov firstMethod (): String {return ("Hej, fra:" + firstProp)} tilsidesæt sjov secondMethod (): String {return ("Hej, fra:" + secondProp + firstProp)}}

Her har vi tilsidesat ejendommen secondProp og funktionen secondFunction som tidligere var defineret i grænsefladen SimpleInterface.

2.3 Implementering af grænseflader gennem delegation

Delegation er et designmønster i objektorienteret programmering for at opnå genanvendelighed af kode gennem komposition i stedet for arv. Selvom dette er muligt at implementere på mange sprog som Java, Kotlin har indbygget support til implementering gennem delegering.

Hvis vi begynder med en grundlæggende grænseflade og klasse:

interface MyInterface {fun someMethod (): String} class MyClass (): MyInterface {override fun someMethod (): String {return ("Hello, World!")}}

Indtil videre intet nyt. Men nu kan vi definere en anden klasse, der implementeres MyInterface gennem delegation:

klasse MyDerivedClass (myInterface: MyInterface): MyInterface af myInterface

MyDerivedClass forventer en delegat som et argument, der faktisk implementerer grænsefladen MyInterface.

Lad os se, hvordan vi kan kalde en funktion af grænsefladen gennem delegeret:

val myClass = MyClass () MyDerivedClass (myClass) .someMethod ()

Her har vi instantificeret Min klasse og brugte det som delegeret til at kalde funktioner i grænsefladen på MyDerivedClass, som faktisk aldrig implementerede disse funktioner direkte.

3. Flere arv

Flere arv er et nøglekoncept i det objektorienterede programmeringsparadigme. Dette gør det muligt for en klasse at arve egenskaber fra mere end et overordnet objekt, f.eks. En grænseflade.

Selvom dette giver mere fleksibilitet i objektmodellering, kommer det med sit eget sæt af kompleksiteter. En sådan er “diamantproblemet”.

Java 8 har sine egne mekanismer til at tackle diamantproblemet, ligesom ethvert andet sprog, der giver mulighed for flere arv.

Lad os se, hvordan Kotlin adresserer det gennem grænseflader.

3.1. Arve flere grænseflader

Vi begynder med at definere to enkle grænseflader:

interface FirstInterface {fun someMethod (): String fun anotherMethod (): String {return ("Hello, from anotherMethod in FirstInterface")}} interface SecondInterface {fun someMethod (): String {return ("Hello, from someMethod in SecondInterface") } sjov anotherMethod (): String {return ("Hej, fra anotherMethod i SecondInterface")}}

Bemærk, at begge grænseflader har metoder med samme kontrakt.

Lad os nu definere en klasse, der arver fra begge disse grænseflader:

klasse SomeClass: FirstInterface, SecondInterface {tilsidesætte sjov someMethod (): String {return ("Hej, fra someMethod i SomeClass")} tilsidesætte sjov anotherMethod (): String {return ("Hej, fra anotherMethod i SomeClass")}}

Som vi kan se, SomeClass implementerer begge dele FirstInterface og SecondInterface. Mens det syntaktisk er ret simpelt, er der lidt semantik, der kræver opmærksomhed her. Vi vil gennemgå dette i det næste underafsnit.

3.2. Løsning af konflikter

Når der implementeres flere grænseflader, kan en klasse arve en funktion, der har en standardimplementering for den samme kontrakt i flere grænseflader. Dette rejser problemet med påkaldelse af denne funktion fra en forekomst af implementeringsklassen.

For at løse denne konflikt kræver Kotlin, at underklassen giver en tilsidesat implementering af sådanne funktioner for at gøre opløsningen eksplicit.

For eksempel, SomeClass ovenstående redskaber en anden metode. Men hvis det ikke gjorde det, ville Kotlin ikke vide, om han skulle påberåbe sig Først eller SecondInterface's standardimplementering af en anden metode. SomeClass skal implementere en anden metode af denne grund.

Imidlertid, nogetMetode er lidt anderledes, da der faktisk ikke er nogen konflikt. FirstInterface giver ikke en standardimplementering for nogetMetode. Det sagt, SomeClass stadig skal implementere det fordi Kotlin tvinger os til at implementere alle arvede funktioner, uanset om de er defineret en eller flere gange i overordnede grænseflader.

3.3. Løsning af diamantproblemet

Et "diamantproblem" opstår, når to underobjekter af et basisobjekt beskriver en bestemt adfærd defineret af basisobjektet. Nu skal et objekt, der arver fra begge disse underordnede objekter, løse, hvilken arvelig adfærd det abonnerer på.

Kotlins løsning på dette problem er gennem de regler, der er defineret for flere arv i det foregående underafsnit. Lad os definere et par grænseflader og en implementeringsklasse til at præsentere diamantproblemet:

interface BaseInterface {fun someMethod (): String} interface FirstChildInterface: BaseInterface {tilsidesætte fun someMethod (): String {return ("Hej, fra someMethod i FirstChildInterface")}} interface SecondChildInterface: BaseInterface {tilsidesætte fun someMethod (): String {return ("Hej, fra someMethod i SecondChildInterface")}} klasse ChildClass: FirstChildInterface, SecondChildInterface {tilsidesætter sjov someMethod (): String {returner super.someMethod ()}}

Her har vi defineret BaseInterface der erklærede en abstrakt funktion kaldet nogetMetode. Begge grænseflader FirstChildInterface og SecondChildInterface arver fra BaseInterface og implementere funktionen nogetMetode.

Nu når vi implementerer Børneklasse arver fra FirstChildInterface og SecondChildInterface, er det nødvendigt for os at tilsidesætte funktionen nogetMetode. Imidlertid, selvom vi skal tilsidesætte metoden, kan vi stadig ringe super som vi gør her med SecondChildInterface.

4. Grænseflader sammenlignet med abstrakte klasser i Kotlin

Abstrakte klasser i Kotlin er klasser, som ikke kan instantieres. Dette kan indeholde en eller flere egenskaber og funktioner. Disse egenskaber og funktioner kan være abstrakte eller konkrete. Enhver klasse, der arver fra en abstrakt klasse, skal implementere alle arvede abstrakte egenskaber og funktioner, medmindre den klasse selv også erklæres som abstrakt.

4.1. Forskelle mellem interface og abstrakt klasse

Vente! Lyder det ikke nøjagtigt som et interface gør?

Faktisk fra starten er en abstrakt klasse ikke meget forskellig fra grænsefladen. Men der er subtile forskelle, der styrer det valg, vi træffer:

  • En klasse i Kotlin kan implementere så mange grænseflader, som de vil, men den kan kun strække sig fra en abstrakt klasse
  • Egenskaber i grænsefladen kan ikke opretholde tilstand, mens de kan i en abstrakt klasse

4.2. Hvornår skal vi bruge hvad?

En grænseflade er kun en plan for at definere klasser, de kan eventuelt også have nogle standardimplementeringer. På den anden side er en abstrakt klasse en ufuldstændig implementering, der afsluttes af de udvidende klasser.

Typisk grænseflader skal bruges til at definere kontrakten, som fremkalder de kapaciteter, det lover at levere. En implementeringsklasse har ansvaret for at levere disse løfter. En abstrakt klasse skal dog bruges til at dele delkarakteristika med udvidede klasser. En udvidende klasse kan tage det videre for at gennemføre det.

5. Sammenligning med Java-grænseflader

Med ændringerne til Java-grænsefladen i Java 8, de er kommet meget tæt på Kotlin-grænseflader. En af vores tidligere artikler indfanger de nye funktioner, der blev introduceret i Java 8, herunder ændringer i grænsefladen.

Der er for det meste syntaktiske forskelle mellem Java og Kotlin-grænseflader nu. En forskel, der skiller sig ud, er relateret til nøgleordet "tilsidesættelse". I Kotlin er det obligatorisk at kvalificere dem med nøgleordet ”mens man implementerer abstrakte egenskaber eller funktioner, der er nedarvet fra en grænseflade.tilsidesætte“. Der er ikke noget sådant eksplicit krav i Java.

6. Konklusion

I denne vejledning diskuterede vi Kotlin-grænseflader, hvordan man definerer og implementerer dem. Derefter talte vi om at arve fra flere grænseflader og den konflikt, de kan skabe. Vi kiggede på, hvordan Kotlin håndterer sådanne konflikter.

Endelig diskuterede vi grænseflader sammenlignet med abstrakte klasser i Kotlin. Vi talte også kort om, hvordan Kotlin-interface sammenligner med Java-interface.

Som altid er koden til eksemplerne tilgængelig på GitHub.


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