Interface ( English interface ) - en program/syntaxstruktur som definierar en relation med objekt som förenas endast av något beteende. När du designar klasser är design av ett gränssnitt detsamma som att designa en specifikation (den uppsättning metoder som varje klass som använder ett gränssnitt måste implementera).
Gränssnitt, tillsammans med abstrakta klasser och protokoll, etablerar ömsesidiga skyldigheter mellan elementen i ett mjukvarusystem, vilket är grunden för konceptet programmering genom kontrakt ( Eng. design by contract , DbC). Ett gränssnitt definierar en interaktionsgräns mellan klasser eller komponenter genom att specificera en viss abstraktion som en implementerare implementerar.
Gränssnittet i OOP är ett strikt formaliserat element i ett objektorienterat språk och används flitigt i källkoden för program.
Gränssnitt tillåter multipelt arv av objekt och löser samtidigt problemet med diamantformat arv . I C++-språket löses det genom klassarv med hjälp av virtual.
Beskrivningen av ett OOP-gränssnitt, förutom detaljerna om syntaxen för specifika språk, består av två delar: namnet och metoderna för gränssnittet.
Gränssnitt kan användas på två sätt:
Som regel, i objektorienterade programmeringsspråk, kan gränssnitt, som klasser, ärvas från varandra. I det här fallet inkluderar det underordnade gränssnittet alla metoder för förfadergränssnittet och lägger eventuellt till sina egna metoder till dem.
Således är ett gränssnitt å ena sidan ett "kontrakt" som klassen som implementerar det åtar sig att uppfylla, å andra sidan är ett gränssnitt en datatyp, eftersom dess beskrivning tydligt nog definierar egenskaperna hos objekt för att kunna skriva in variabler på lika villkor med klassen. Det bör dock betonas att ett gränssnitt inte är en komplett datatyp, eftersom det bara definierar objektens yttre beteende. Den interna strukturen och implementeringen av beteendet som specificeras av gränssnittet tillhandahålls av klassen som implementerar gränssnittet; det är därför det inte finns några "gränssnittsinstanser" i dess rena form, och varje variabel av typen "gränssnitt" innehåller instanser av konkreta klasser.
Användningen av gränssnitt är ett alternativ för att tillhandahålla polymorfism i objektspråk och miljöer. Alla klasser som implementerar samma gränssnitt, i termer av beteendet de definierar, beter sig på samma sätt externt. Detta låter dig skriva generaliserade databehandlingsalgoritmer som använder gränssnittsparametrar som typer och applicera dem på objekt av olika typer, varje gång du får det önskade resultatet.
Till exempel kan " "-gränssnittet Cloneablebeskriva abstraktionen av kloning (skapa exakta kopior) av objekt genom att specificera en metod " Clone" som ska kopiera innehållet i ett objekt till ett annat objekt av samma typ. Då måste alla klasser vars objekt kan behöva kopieras implementera gränssnittet Cloneableoch tillhandahålla en metod Clone, och var som helst i programmet där objektkloning krävs anropas metoden på objektet för detta ändamål Clone. Dessutom behöver koden som använder denna metod bara ha en beskrivning av gränssnittet, den kanske inte vet något om den faktiska klassen vars objekt kopieras. Således tillåter gränssnitt dig att dela upp ett mjukvarusystem i moduler utan ömsesidigt kodberoende.
Det kan ses att ett gränssnitt, från en formell synvinkel, bara är en ren abstrakt klass , det vill säga en klass där ingenting är definierat förutom abstrakta metoder . Om ett programmeringsspråk stöder flera nedärvnings- och abstrakta metoder (som till exempel C++ ), så finns det inget behov av att införa ett separat koncept för "gränssnitt" i språkets syntax. Dessa entiteter beskrivs med hjälp av abstrakta klasser och ärvs av klasser för att implementera abstrakta metoder.
Att stödja multipelarv i sin helhet är dock ganska komplicerat och orsakar många problem, både på språkimplementeringsnivån och på applikationsarkitekturnivån. Introduktionen av konceptet med gränssnitt är en kompromiss som gör att du kan få många av fördelarna med multipelt arv (särskilt förmågan att bekvämt definiera logiskt relaterade uppsättningar av metoder som klassliknande enheter som tillåter arv och implementering), utan att implementera den i sin helhet och därmed utan att stöta på de flesta av de svårigheter som är förknippade med den.
På exekveringsnivån orsakar det klassiska schemat med multipelt arv ytterligare ett antal olägenheter:
Att använda ett schema med gränssnitt (istället för multipelt arv) undviker dessa problem, förutom frågan om anrop av gränssnittsmetoder (det vill säga virtuella metodanrop i multipelt arv, se ovan). Den klassiska lösningen är (till exempel i JVM för Java eller CLR för C#) att gränssnittsmetoder anropas på ett mindre effektivt sätt, utan hjälp av en virtuell tabell: med varje anrop bestäms först en specifik objektklass, och sedan söks den önskade metoden i den (naturligtvis med många optimeringar).
Vanligtvis tillåter programmeringsspråk att ett gränssnitt ärvs från flera förfädersgränssnitt. Alla metoder som deklareras i förfädersgränssnitt blir en del av deklarationen av det underordnade gränssnittet. Till skillnad från klassarv är multipel nedärvning av gränssnitt mycket lättare att implementera och orsakar inga betydande svårigheter.
En kollision med flera arv av gränssnitt och med implementering av flera gränssnitt av en klass är dock fortfarande möjlig. Det inträffar när två eller flera gränssnitt ärvt av ett nytt gränssnitt eller implementerat av en klass har metoder med samma signatur. Utvecklare av programmeringsspråk tvingas välja för sådana fall vissa metoder för att lösa motsägelser. Det finns flera alternativ här: ett förbud mot implementering, en explicit indikation på en specifik, och en implementering av basgränssnittet eller klassen.
Implementeringen av gränssnitt bestäms till stor del av språkets initiala kapacitet och syftet med vilket gränssnitt introduceras i det. Funktionerna med att använda gränssnitt i Java , Object Pascal , Delphi och C++ är mycket vägledande , eftersom de visar tre fundamentalt olika situationer: språkets initiala orientering för att använda konceptet med gränssnitt, deras användning för kompatibilitet och deras emulering av klasser.
Gränssnitt introducerades i Delphi för att stödja Microsofts COM - teknik . Men när Kylix släpptes frikopplades gränssnitt som en del av språket från COM-teknik. Alla gränssnitt ärver från gränssnittet [1] , som på win32-plattformen är detsamma som standard COM-gränssnittet med samma namn, precis som alla klasser i det är ättlingar till klassen . Den explicita användningen av IUnknown som en förfader är reserverad för kod som använder COM-teknik. IInterface IUnknownTObject
Exempel på gränssnittsdeklaration:
IMyInterface = gränssnittsprocedur DoSomething ; _ slut ;För att deklarera implementeringen av gränssnitt måste du i klassbeskrivningen ange deras namn inom parentes efter nyckelordet class, efter namnet på förfaderklassen. Eftersom "ett gränssnitt är ett kontrakt som ska uppfyllas" kompileras inte programmet förrän det är implementerat i implementeringsklassenprocedure DoSomething;
Den tidigare nämnda fokuseringen av Delphi-gränssnitt på COM-teknik har lett till en del besvär. Faktum är att gränssnittet IInterface(från vilket alla andra gränssnitt ärvs) redan innehåller tre metoder som är obligatoriska för COM-gränssnitt: QueryInterface, _AddRef, _Release. Därför måste varje klass som implementerar vilket gränssnitt som helst implementera dessa metoder, även om, enligt programmets logik, gränssnittet och klassen inte har något med COM att göra. Det bör noteras att dessa tre metoder också används för att kontrollera ett objekts livslängd och implementera mekanismen för gränssnittsbegäran via " as"-operatören.
Ett exempel på en klass som implementerar ett gränssnitt:
TMyClass = class ( TMyParentClass , IMyInterface ) procedur DoSomething ; function QueryInterface ( const IID : TGUID ; out Obj ) : HResult ; stdcall ; function _AddRef : Heltal ; stdcall ; function _Release : Heltal ; stdcall ; slut ; genomförandeProgrammeraren måste implementera metoderna QueryInterface, _AddRef, _Release. För att bli av med behovet av att skriva standardmetoder tillhandahålls en biblioteksklass TInterfacedObject - den implementerar ovanstående tre metoder, och varje klass som ärver från den och dess avkomlingar får denna implementering. Implementeringen av dessa metoder TInterfacedObjectförutsätter automatisk kontroll över objektets livslängd genom att räkna referenser genom metoderna _AddRefoch _Release, som anropas automatiskt när man går in i och lämnar omfånget.
Ett exempel på en klassarvinge TInterfacedObject:
TMyClass = class ( TInterfacedObject , IMyInterface ) procedur DoSomething ; slut ;När man ärver en klass som implementerar ett gränssnitt från en klass utan gränssnitt, måste programmeraren implementera de metoder som nämns manuellt, bestämma närvaron eller frånvaron av referensräkningskontroll, samt erhålla gränssnittet i QueryInterface.
Ett exempel på en godtycklig klass utan referensräkning:
TMyClass = class ( TObject , IInterface , IMyInterface ) //IInterface function QueryInterface ( const IID : TGUID ; out Obj ) : HResult ; stdcall ; function _AddRef : Heltal ; stdcall ; function _Release : Heltal ; stdcall ; //IMyInterface- proceduren DoSomething ; slut ; { TMyClass } funktion TMyClass . QueryInterface ( const IID : TGUID ; out Obj ) : HResult ; börja om GetInterface ( IID , Obj ) sedan Resultat := 0 annars Resultat := E_NOINTERFACE ; slut ; funktion TMyClass . _AddRef : Heltal ; börja Resultat := - 1 ; slut ; funktion TMyClass . _Release : Heltal ; börja Resultat := - 1 ; slut ; procedur TMyClass . Gör något ; börja //Gör något slut ;C++ stöder flera arv och abstrakta klasser , så som nämnts ovan behövs inte en separat syntaktisk konstruktion för gränssnitt på detta språk. Gränssnitt definieras med hjälp av abstrakta klasser , och implementeringen av ett gränssnitt görs genom att ärva från dessa klasser.
Exempel på gränssnittsdefinition :
/** * interface.Openable.h * */ #ifndef INTERFACE_OPENABLE_HPP #define INTERFACE_OPENABLE_HPP // iOpenable gränssnittsklass. Avgör om något kan öppnas/stängas. klass iOpenable { offentliga : virtuell ~ iOpenable (){} virtuell void öppen () = 0 ; virtual void close () = 0 ; }; #endifEtt gränssnitt implementeras genom arv (på grund av närvaron av flera arv är det möjligt att implementera flera gränssnitt i en klass , om det behövs; i exemplet nedan är arv inte flera):
/** * class.Door.h * */ #include "interface.openable.h" #include <iostream> klass Dörr : offentlig iOpenable { offentliga : Dörr (){ std :: cout << "Dörrobjekt skapat" << std :: endl ;} virtuell ~ Dörr (){} //Öka iOpenable-gränssnittsmetoderna för Door-klassen virtual void open (){ std :: cout << "Door opened" << std :: endl ;} virtual void close (){ std :: cout << "Dörr stängd" << std :: endl ;} //Dörrklassspecifika egenskaper och metoder std :: string mMaterial ; std :: sträng mColor ; //... }; /** * class.Book.h * */ #include "interface.openable.h" #include <iostream> klassbok : offentlig iOpenable _ { offentliga : Bok (){ std :: cout << "Bokobjekt skapat" << std :: endl ;} virtuell ~ Bok (){} //Öka iOpenable-gränssnittsmetoderna för bokklassen virtual void open (){ std :: cout << "Book opened" << std :: endl ;} virtual void close (){ std :: cout << "Boken stängd" << std :: endl ;} //Bokspecifika egenskaper och metoder std :: string mTitle ; std :: sträng mAuthor ; //... };Låt oss testa allt tillsammans:
/** * test.openable.cpp * */ #include "interface.openable.h" #inkludera "class.Door.h" #inkludera "class.book.h" //Funktionen att öppna/stänga alla heterogena objekt som implementerar iOpenable-gränssnittet void openAndCloseSomething ( iOpenable & smth ) { smth . öppna (); smth . stäng (); } int main () { Door myDoor ; BookmyBook ; _ openAndCloseSomething ( myDoor ); openAndCloseSomething ( myBook ); system ( "paus" ); returnera 0 ; }Till skillnad från C++ tillåter Java dig inte att ärva mer än en klass. Som ett alternativ till multipelarv finns det gränssnitt. Varje klass i Java kan implementera vilken uppsättning gränssnitt som helst. Det är inte möjligt att härleda objekt från gränssnitt i Java.
GränssnittsdeklarationerEn gränssnittsdeklaration är mycket lik en förenklad klassdeklaration.
Det börjar med en titel. Modifierare listas först . Ett gränssnitt kan deklareras som public, i vilket fall det är tillgängligt för allmänt bruk, eller så kan en åtkomstmodifierare utelämnas, i vilket fall gränssnittet endast är tillgängligt för typer i dess . En gränssnittsmodifierare abstractkrävs inte eftersom alla gränssnitt är abstrakta klasser . Det kan specificeras, men det rekommenderas inte att göra det för att inte belamra .
Därefter interfaceskrivs nyckelordet och gränssnittsnamnet.
Detta kan följas av ett nyckelord extendsoch en lista över gränssnitt som det deklarerade gränssnittet kommer att ärva från. Det kan finnas många föräldratyper (klasser och/eller gränssnitt) - huvudsaken är att det inte finns några upprepningar, och att arvsförhållandet inte bildar ett cykliskt beroende.
Gränssnittsarv är verkligen mycket flexibelt. Så, om det finns två gränssnitt, Aoch B, och Bärvs från A, då kan det nya gränssnittet Cärvas från dem båda. Det är dock tydligt att när man ärver från B, är det redundant att indikera arv från A, eftersom alla element i detta gränssnitt redan kommer att ärvas genom gränssnitt B.
Sedan skrivs gränssnittets kropp inom parenteser.
Exempel på gränssnittsdeklaration (Fel om klasserna Colorable och Resizable: Typen Colorable and Resizable kan inte vara ett supergränssnitt av Drawable; ett supergränssnitt måste vara ett gränssnitt):
offentligt gränssnitt Drawable utökar Färgbar , Kan ändra storlek { }Gränssnittets kropp består av deklarationen av element, det vill säga fält - konstanter och abstrakta metoder . Alla gränssnittsfält är automatiskt public final static, så dessa modifierare är valfria och till och med oönskade för att inte röra ihop koden. Eftersom fälten är slutgiltiga måste de initieras omedelbart .
offentligt gränssnitt Vägbeskrivning { int HÖGER = 1 ; int LEFT = 2 ; int UPP = 3 ; int DOWN = 4 ; }Alla gränssnittsmetoder är public abstract, och dessa modifierare är också valfria.
offentligt gränssnitt Moveable { void moveRight (); void flytta Vänster (); void moveUp (); void moveDown (); }Som du kan se är gränssnittsbeskrivningen mycket enklare än klassdeklarationen.
GränssnittsimplementeringFör att implementera ett gränssnitt måste det specificeras i klassdeklarationen med hjälp av implements. Exempel:
gränssnitt I { void interfaceMethod (); } public class ImplementingInterface implementerar I { void interfaceMethod () { System . ut . println ( "Denna metod implementeras från gränssnitt I" ); } } public static void main ( String [] args ) { ImplementingInterface temp = new ImplementingInterface (); temp . interfaceMethod (); }Varje klass kan implementera alla tillgängliga gränssnitt. Samtidigt måste alla abstrakta metoder som dykt upp när man ärvde från gränssnitt eller en överordnad klass implementeras i klassen så att den nya klassen kan deklareras som icke-abstrakt.
Om metoder med samma signatur ärvs från olika källor räcker det att beskriva implementeringen en gång, och den kommer att tillämpas på alla dessa metoder. Men om de har ett annat returvärde uppstår en konflikt. Exempel:
gränssnitt A { int getValue (); } gränssnitt B { double getValue (); } gränssnitt C { int getValue (); } public class Korrekt implementerar A , C // klassen ärver korrekt metoder med samma signatur { int getValue () { return 5 ; } } class Fel implementerar A , B // klass kastar ett kompileringsfel { int getValue () { return 5 ; } double getValue () { return 5.5 ; } }I C# kan gränssnitt ärva från ett eller flera andra gränssnitt. Gränssnittsmedlemmar kan vara metoder, egenskaper, händelser och indexerare:
gränssnitt I1 { void Method1 (); } gränssnitt I2 { void Method2 (); } gränssnitt I : I1 , I2 { void Metod (); int Räkna { få ; } event EventHandler SomeEvent ; sträng denna [ int index ] { get ; set ; } }När ett gränssnitt implementeras måste en klass implementera både metoderna för själva gränssnittet och dess basgränssnitt:
public class C : I { public void Method () { } public int Count { get { throw new NotImplementedException (); } } offentligt evenemang EventHandler SomeEvent ; public string this [ int index ] { get { throw new NotImplementedException (); } set { throw new NotImplementedException (); } } public void Metod1 () { } public void Metod2 () { } }Gränssnitt i UML används för att visualisera, specificera, konstruera och dokumentera UML-dockningsnoder mellan komponentdelarna i ett system. Typer och UML-roller tillhandahåller en mekanism för att modellera den statiska och dynamiska kartläggningen av en abstraktion till ett gränssnitt i ett givet sammanhang.
I UML avbildas gränssnitt som klasser med stereotypen "gränssnitt", eller som cirklar (i detta fall visas inte UML-operationerna i gränssnittet).
Datatyper | |
---|---|
Otolkbart | |
Numerisk | |
Text | |
Referens | |
Sammansatt | |
abstrakt | |
Övrig | |
Relaterade ämnen |