Interface (objektorienteret programmering)

Den aktuelle version af siden er endnu ikke blevet gennemgået af erfarne bidragydere og kan afvige væsentligt fra den version , der blev gennemgået den 7. december 2017; checks kræver 27 redigeringer .

Interface ( engelsk  interface ) - et program/syntaksstruktur, der definerer et forhold til objekter, der kun forenes af en eller anden adfærd. Når du designer klasser, er design af en grænseflade det samme som at designe en specifikation (det sæt af metoder, som hver klasse, der bruger en grænseflade , skal implementere).

Grænseflader etablerer sammen med abstrakte klasser og protokoller gensidige forpligtelser mellem elementerne i et softwaresystem, som er grundlaget for konceptet med programmering ved kontrakt ( Eng.  design by contract , DbC). En grænseflade definerer en interaktionsgrænse mellem klasser eller komponenter ved at specificere en bestemt abstraktion , som en implementer implementerer.

Interfacet i OOP er et strengt formaliseret element i et objektorienteret sprog og er meget udbredt i programmers kildekode.

Grænseflader tillader multipel nedarvning af objekter og løser samtidig problemet med diamantformet arv . I C++ sproget løses det gennem klassearv ved hjælp af virtual.

Beskrivelse og brug af grænseflader

Beskrivelsen af ​​en OOP-grænseflade, bortset fra detaljerne i syntaksen for specifikke sprog, består af to dele: grænsefladens navn og metoder .

Grænseflader kan bruges på to måder:

Som regel kan grænseflader, som klasser, i objektorienterede programmeringssprog arves fra hinanden. I dette tilfælde inkluderer den underordnede grænseflade alle metoderne i forfadergrænsefladen og tilføjer eventuelt sine egne metoder til dem.

Således er en grænseflade på den ene side en "kontrakt", som klassen , der implementerer den forpligter sig til at opfylde, på den anden side er en grænseflade en datatype, fordi dens beskrivelse klart nok definerer egenskaberne af objekter for at kunne skrive variable på lige fod med klassen. Det skal dog understreges, at en grænseflade ikke er en komplet datatype, da den kun definerer objekters eksterne adfærd. Den interne struktur og implementering af adfærden specificeret af grænsefladen leveres af klassen, der implementerer grænsefladen; det er derfor, der ikke er nogen "interface-instanser" i dens rene form, og enhver variabel af typen "interface" indeholder instanser af konkrete klasser.

Brugen af ​​grænseflader er en mulighed for at give polymorfi i objektsprog og miljøer. Alle klasser, der implementerer den samme grænseflade, med hensyn til den adfærd, de definerer, opfører sig på samme måde eksternt. Dette giver dig mulighed for at skrive generaliserede databehandlingsalgoritmer, der bruger grænsefladeparametre som typer og anvende dem på objekter af forskellige typer, hver gang du opnår det ønskede resultat.

For eksempel kan " "-grænsefladen Cloneablebeskrive abstraktionen af ​​kloning (oprettelse af nøjagtige kopier) af objekter ved at specificere en metode " Clone", der skal kopiere indholdet af et objekt til et andet objekt af samme type. Så skal enhver klasse, hvis objekter muligvis skal kopieres, implementere grænsefladen Cloneableog levere en metode Clone, og hvor som helst i programmet, hvor objektkloning er påkrævet, kaldes metoden på objektet til dette formål Clone. Desuden skal koden, der bruger denne metode, kun have en beskrivelse af grænsefladen, den ved muligvis ikke noget om den faktiske klasse, hvis objekter er kopieret. Således giver grænseflader dig mulighed for at opdele et softwaresystem i moduler uden gensidig kodeafhængighed.

Grænseflader og abstrakte klasser

Det kan ses, at en grænseflade fra et formelt synspunkt blot er en ren abstrakt klasse , det vil sige en klasse, hvor intet er defineret undtagen abstrakte metoder . Hvis et programmeringssprog understøtter flere nedarvnings- og abstrakte metoder (som f.eks. C++ ), er der ingen grund til at indføre et separat begreb "grænseflade" i sprogets syntaks. Disse entiteter beskrives ved hjælp af abstrakte klasser og nedarves af klasser for at implementere abstrakte metoder.

Det er dog ret komplekst at understøtte multiple nedarvning fuldt ud og forårsager mange problemer, både på sprogimplementeringsniveauet og på applikationsarkitekturniveauet. Introduktionen af ​​begrebet grænseflader er et kompromis, der giver dig mulighed for at få mange af fordelene ved multipel nedarvning (især evnen til bekvemt at definere logisk relaterede sæt af metoder som klasselignende entiteter, der tillader nedarvning og implementering), uden at implementere den i sin helhed og dermed uden at støde på de fleste vanskeligheder forbundet med den.

På udførelsesniveauet forårsager den klassiske ordning med multipel arv et yderligere antal gener:

Brug af et skema med grænseflader (i stedet for multipel nedarvning) undgår disse problemer, bortset fra spørgsmålet om at kalde grænseflademetoder (det vil sige virtuelle metodekalder multipel nedarvning, se ovenfor). Den klassiske løsning er (for eksempel i JVM for Java eller CLR for C#), at grænseflademetoder kaldes på en mindre effektiv måde uden hjælp fra en virtuel tabel: med hvert kald bestemmes først en specifik objektklasse, og derefter søges den ønskede metode i den (selvfølgelig med adskillige optimeringer).

Flere nedarvnings- og grænsefladeimplementeringer

Typisk tillader programmeringssprog en grænseflade at blive nedarvet fra flere forfædres grænseflader. Alle metoder, der er erklæret i ancestor-grænseflader, bliver en del af erklæringen af ​​den underordnede grænseflade. I modsætning til klassearv er multipel nedarvning af grænseflader meget nemmere at implementere og forårsager ikke væsentlige vanskeligheder.

Men én kollision med flere nedarvninger af grænseflader og med implementering af flere grænseflader af én klasse er stadig mulig. Det opstår, når to eller flere grænseflader, der er nedarvet af en ny grænseflade eller implementeret af en klasse, har metoder med samme signatur. Udviklere af programmeringssprog er tvunget til i sådanne tilfælde at vælge visse metoder til at løse modsætninger. Der er flere muligheder her: et forbud mod implementering, en eksplicit angivelse af en specifik og en implementering af basisgrænsefladen eller klassen.

Grænseflader på specifikke sprog og systemer

Implementeringen af ​​grænseflader er i vid udstrækning bestemt af sprogets indledende evner og formålet med hvilke grænseflader indføres i det. Funktionerne ved at bruge grænseflader i Java , Object Pascal , Delphi og C++ er meget vejledende , da de demonstrerer tre fundamentalt forskellige situationer: sprogets indledende orientering til at bruge begrebet grænseflader, deres brug for kompatibilitet og deres emulering efter klasser.

Delphi

Grænseflader blev introduceret i Delphi for at understøtte Microsofts COM - teknologi . Men da Kylix blev udgivet , blev grænseflader som et element i sproget afkoblet fra COM-teknologi. Alle interfaces arver fra [1] interfacet , som på win32 platformen er det samme som standard COM interface af samme navn, ligesom alle klasser i den er efterkommere af klassen . Den eksplicitte brug af IUnknown som en forfader er forbeholdt kode ved hjælp af COM-teknologi. IInterface IUnknownTObject

Eksempel på grænsefladeerklæring:

IMyInterface = grænsefladeprocedure DoSomething ; _ ende ;

For at erklære implementeringen af ​​grænseflader skal du i klassebeskrivelsen angive deres navne i parentes efter nøgleordet class, efter navnet på stamfaderklassen. Da "en grænseflade er en kontrakt, der skal opfyldes", kompilerer programmet ikke, før det er implementeret i implementeringsklassenprocedure DoSomething;

Det førnævnte fokus på Delphi-grænseflader på COM-teknologi har ført til nogle besvær. Faktum er, at grænsefladen IInterface(hvorfra alle andre grænseflader er nedarvet) allerede indeholder tre metoder, der er obligatoriske for COM-grænseflader: QueryInterface, _AddRef, _Release. Derfor skal enhver klasse, der implementerer en hvilken som helst grænseflade, implementere disse metoder, selvom grænsefladen og klassen ikke har noget med COM at gøre ifølge programmets logik. Det skal bemærkes, at disse tre metoder også bruges til at kontrollere et objekts levetid og implementere grænsefladeanmodningsmekanismen gennem asoperatøren " ".

Et eksempel på en klasse, der implementerer en grænseflade:

TMyClass = klasse ( TMyParentClass , IMyInterface ) procedure DoSomething ; function QueryInterface ( const IID : TGUID ; out Obj ) : HResult ; stdcall ; function _AddRef : Heltal ; stdcall ; function _Release : Heltal ; stdcall ; ende ; implementering

Programmøren skal implementere metoderne korrekt QueryInterface, _AddRef, _Release. For at slippe af med behovet for at skrive standardmetoder leveres en biblioteksklasse TInterfacedObject - den implementerer de tre ovennævnte metoder, og enhver klasse, der arver fra den og dens efterkommere, modtager denne implementering. Implementeringen af ​​disse metoder TInterfacedObjectforudsætter automatisk kontrol over objektets levetid ved at tælle referencer gennem metoderne _AddRefog _Release, som kaldes automatisk, når man går ind og ud af scope.

Et eksempel på en klassearving TInterfacedObject:

TMyClass = klasse ( TInterfacedObject , IMyInterface ) procedure DoSomething ; ende ;

Når man arver en klasse, der implementerer en grænseflade fra en klasse uden grænseflader, skal programmøren implementere de nævnte metoder manuelt, bestemme tilstedeværelsen eller fraværet af referencetællingskontrol, samt opnå grænsefladen i QueryInterface.

Et eksempel på en vilkårlig klasse uden referencetælling:

TMyClass = klasse ( TObject , IInterface , IMyInterface ) //IInterface funktion QueryInterface ( const IID : TGUID ; out Obj ) : HResult ; stdcall ; function _AddRef : Heltal ; stdcall ; function _Release : Heltal ; stdcall ; //IMyInterface procedure DoSomething ; ende ; { TMyClass } funktion TMyClass . QueryInterface ( const IID : TGUID ; out Obj ) : HResult ; start hvis GetInterface ( IID , Obj ) Resultat := 0 ellers Resultat := E_NOINTERFACE ; ende ; funktion TMyClass . _AddRef : Heltal ; begynde Resultat := - 1 ; ende ; funktion TMyClass . _Release : Heltal ; begynde Resultat := - 1 ; ende ; procedure TMyClass . Gør Noget ; begynde //Gør noget slut ;

C++

C++ understøtter flere nedarvnings- og abstrakte klasser , så som nævnt ovenfor er en separat syntaktisk konstruktion til grænseflader på dette sprog ikke nødvendig. Grænseflader defineres ved hjælp af abstrakte klasser , og implementeringen af ​​en grænseflade sker ved at nedarve fra disse klasser.

Eksempel på grænsefladedefinition :

/** * interface.Openable.h * */ #ifndef INTERFACE_OPENABLE_HPP #define INTERFACE_OPENABLE_HPP // iOpenable interface klasse. Bestemmer om noget kan åbnes/lukkes. klasse iOpenable { offentligt : virtuel ~ iOpenable (){} virtuel void åben () = 0 ; virtual void close () = 0 ; }; #Afslut Hvis

En grænseflade implementeres gennem nedarvning (på grund af tilstedeværelsen af ​​multipel nedarvning er det muligt at implementere flere grænseflader i én klasse , hvis det er nødvendigt; i eksemplet nedenfor er nedarvning ikke multipel):

/** * klasse.Dør.h * */ #include "interface.openable.h" #include <iostream> klasse Dør : offentlig iOpenable { offentligt : Dør (){ std :: cout << "Dørobjekt oprettet" << std :: endl ;} virtuel ~ dør (){} //Forøgelse af iOpenable-grænseflademetoderne for Door-klassen virtual void open (){ std :: cout << "Door opened" << std :: endl ;} virtual void close (){ std :: cout << "Dør lukket" << std :: endl ;} //Dørklassespecifikke egenskaber og metoder std :: string mMaterial ; std :: streng mColor ; //... }; /** * class.Book.h * */ #include "interface.openable.h" #include <iostream> klassebog : offentlig iOpenable _ { offentligt : Bog (){ std :: cout << "Bogobjekt oprettet" << std :: endl ;} virtuel ~ Bog (){} //Forøgelse af iOpenable-grænseflademetoderne for Book-klassen virtual void open (){ std :: cout << "Book opened" << std :: endl ;} virtual void close (){ std :: cout << "Bog lukket" << std :: endl ;} //Bogspecifikke egenskaber og metoder std :: string mTitle ; std :: streng mAuthor ; //... };

Lad os teste alt sammen:

/** * test.openable.cpp * */ #include "interface.openable.h" #include "class.Door.h" #include "class.book.h" //Funktionen med at åbne/lukke alle heterogene objekter, der implementerer iOpenable-grænsefladen void openAndCloseSomething ( iOpenable & smth ) { smth . åben (); smth . lukke (); } int main () { Dør myDoor ; BookmyBook ; _ openAndCloseSomething ( minDør ); openAndCloseSomething ( myBook ); system ( "pause" ); returnere 0 ; }

Java

I modsætning til C++ tillader Java dig ikke at arve mere end én klasse. Som et alternativ til multipel arv er der grænseflader. Hver klasse i Java kan implementere ethvert sæt grænseflader. Det er ikke muligt at udlede objekter fra grænseflader i Java.

Grænsefladeerklæringer

En grænsefladedeklaration minder meget om en forenklet klassedeklaration.

Det starter med en titel. Modifikatorer vises først . En grænseflade kan erklæres som public, i hvilket tilfælde den er tilgængelig til offentlig brug, eller en adgangsmodifikator kan udelades, i hvilket tilfælde grænsefladen kun er tilgængelig for typer i dens . En grænseflademodifikator abstracter ikke påkrævet, fordi alle grænseflader er abstrakte klasser . Det kan specificeres, men det anbefales ikke at gøre det for ikke at rode .

interfaceDernæst skrives nøgleordet og interfacenavnet.

Dette kan efterfølges af et nøgleord extendsog en liste over grænseflader, som den erklærede grænseflade vil arve fra. Der kan være mange forældretyper (klasser og/eller grænseflader) - det vigtigste er, at der ikke er gentagelser, og at arveforholdet ikke danner en cyklisk afhængighed.

Interface arv er faktisk meget fleksibel. Så hvis der er to grænseflader, Aog B, og Ber nedarvet fra A, så kan den nye grænseflade Cnedarves fra dem begge. Det er dog klart, at når man nedarver fra B, er det overflødigt at angive arv fra A, da alle elementer i denne grænseflade allerede vil blive nedarvet gennem grænseflade B.

Derefter skrives grænsefladens krop i krøllede parenteser.

Eksempel på grænsefladedeklaration (Fejl hvis klasserne Colorable og Resizable: Typen Colorable and Resizable kan ikke være en supergrænseflade af Drawable; en supergrænseflade skal være en grænseflade):

offentlig grænseflade Drawable udvider Farverbar , kan ændres størrelse { }

Grænsefladens krop består af erklæringen af ​​elementer, det vil sige felt - konstanter og abstrakte metoder . Alle grænsefladefelter er automatisk public final static, så disse modifikatorer er valgfrie og endda uønskede for ikke at rode i koden. Da felterne er endelige, skal de initialiseres med det samme .

offentlig grænseflade Retninger { int HØJRE = 1 ; int VENSTRE = 2 ; int OP = 3 ; int NED = 4 ; }

Alle grænseflademetoder er public abstract, og disse modifikatorer er også valgfrie.

offentlig grænseflade Moveable { void moveRight (); void flytte Venstre (); void moveUp (); void moveDown (); }

Som du kan se, er grænsefladebeskrivelsen meget enklere end klasseerklæringen.

Grænsefladeimplementering

For at implementere en grænseflade skal den angives i klasseerklæringen ved hjælp af implements. Eksempel:

interface I { void interfaceMethod (); } public class ImplementingInterface implementerer I { void interfaceMethod () { System . ud . println ( "Denne metode er implementeret fra interface I" ); } } public static void main ( String [] args ) { ImplementingInterface temp = new ImplementingInterface (); temp . interfaceMethod (); }

Hver klasse kan implementere alle tilgængelige grænseflader. Samtidig skal alle abstrakte metoder, der dukkede op ved nedarvning fra interfaces eller en overordnet klasse , implementeres i klassen , så den nye klasse kan erklæres ikke-abstrakt.

Hvis metoder med samme signatur er nedarvet fra forskellige kilder , så er det nok at beskrive implementeringen én gang, og den vil blive anvendt på alle disse metoder. Men hvis de har en anden returværdi, opstår der en konflikt. Eksempel:

interface A { int getValue (); } interface B { double getValue (); } interface C { int getValue (); } public class Korrekt implementerer A , C // klassen arver korrekt metoder med samme signatur { int getValue () { return 5 ; } } klasse Forkert implementerer A , B // klasse kaster en kompileringstidsfejl { int getValue () { return 5 ; } double getValue () { return 5.5 ; } }

C#

I C# kan grænseflader arve fra en eller flere andre grænseflader. Interfacemedlemmer kan være metoder, egenskaber, hændelser og indeksere:

interface I1 { void Method1 (); } interface I2 { void Method2 (); } interface I : I1 , I2 { void Method (); int Count { ; } begivenhed EventHandler SomeEvent ; streng denne [ int indeks ] { get ; sæt ; } }

Når du implementerer en grænseflade, skal en klasse implementere både metoderne for selve grænsefladen og dens basisgrænseflader:

public class C : I { public void Method () { } public int Count { get { throw new NotImplementedException (); } } offentlig begivenhed EventHandler SomeEvent ; public string this [ int index ] { get { throw new NotImplementedException (); } set { throw new NotImplementedException (); } } offentlig ugyldig metode1 () { } offentlig ugyldig metode2 () { } }

Grænseflader i UML

Grænseflader i UML bruges til at visualisere, specificere, konstruere og dokumentere UML docking noder mellem komponentdelene af et system. Typer og UML-roller giver en mekanisme til modellering af den statiske og dynamiske kortlægning af en abstraktion til en grænseflade i en given kontekst.

I UML er grænseflader afbildet som klasser med "grænseflade"-stereotypen eller som cirkler (i dette tilfælde vises UML-handlingerne i grænsefladen ikke).

Se også

Noter

Links