Interfață (programare orientată pe obiecte)

Versiunea actuală a paginii nu a fost încă revizuită de colaboratori experimentați și poate diferi semnificativ de versiunea revizuită pe 7 decembrie 2017; verificările necesită 27 de modificări .

Interfață ( interfață engleză  ) - o structură de program/sintaxă care definește o relație cu obiecte care sunt unite doar printr-un anumit comportament. La proiectarea claselor, proiectarea unei interfețe este la fel cu proiectarea unei specificații (setul de metode pe care trebuie să le implementeze fiecare clasă care utilizează o interfață).

Interfețele, împreună cu clasele și protocoalele abstracte, stabilesc obligații reciproce între elementele unui sistem software, care stă la baza conceptului de programare prin contract ( Eng.  design by contract , DbC). O interfață definește o graniță de interacțiune între clase sau componente prin specificarea unei anumite abstractizări pe care o implementează un implementator.

Interfața în OOP este un element strict formalizat al unui limbaj orientat pe obiecte și este utilizată pe scară largă în codul sursă al programelor.

Interfețele permit moștenirea multiplă a obiectelor și, în același timp, rezolvă problema moștenirii în formă de diamant . În limbajul C++, se rezolvă prin moștenirea clasei folosind virtual.

Descrierea și utilizarea interfețelor

Descrierea unei interfețe OOP, în afară de detaliile sintaxei unor limbaje specifice, constă din două părți: numele și metodele interfeței.

Interfețele pot fi utilizate în două moduri:

De regulă, în limbajele de programare orientate pe obiecte, interfețele, precum clasele, pot fi moștenite unele de la altele. În acest caz, interfața copil include toate metodele interfeței strămoșilor și, opțional, le adaugă propriile metode.

Astfel, pe de o parte, o interfață este un „contract” pe care clasa care o implementează se obligă să-l îndeplinească, pe de altă parte, o interfață este un tip de date, deoarece descrierea sa definește suficient de clar proprietățile obiectelor pentru a le tipa. variabile în mod egal cu clasa. Cu toate acestea, trebuie subliniat că o interfață nu este un tip de date complet, deoarece definește doar comportamentul extern al obiectelor. Structura internă și implementarea comportamentului specificat de interfață este asigurată de clasa care implementează interfața; de aceea nu există „instanțe de interfață” în forma sa pură, iar orice variabilă de tip „interfață” conține instanțe de clase concrete.

Utilizarea interfețelor este o opțiune pentru furnizarea de polimorfism în limbaje și medii obiect . Toate clasele care implementează aceeași interfață, în ceea ce privește comportamentul pe care îl definesc, se comportă în același mod în exterior. Acest lucru vă permite să scrieți algoritmi generalizați de procesare a datelor care folosesc parametrii de interfață ca tipuri și să îi aplicați obiectelor de diferite tipuri, obținând de fiecare dată rezultatul cerut.

De exemplu, interfața „ ” Cloneablepoate descrie abstractizarea clonării (crearea de copii exacte) a obiectelor prin specificarea unei metode „ Clone” care ar trebui să copieze conținutul unui obiect într-un alt obiect de același tip. Apoi, orice clasă ale cărei obiecte ar putea avea nevoie să fie copiate trebuie să implementeze interfața Cloneableși să furnizeze o metodă Clone, iar oriunde în program în care este necesară clonarea obiectului, metoda este apelată pe obiect în acest scop Clone. Mai mult, codul care folosește această metodă trebuie să aibă doar o descriere a interfeței, este posibil să nu știe nimic despre clasa reală ale cărei obiecte sunt copiate. Astfel, interfețele vă permit să divizați un sistem software în module fără dependență reciprocă de cod.

Interfețe și clase abstracte

Se poate observa că o interfață, din punct de vedere formal, este doar o clasă abstractă pură , adică o clasă în care nimic nu este definit în afară de metode abstracte . Dacă un limbaj de programare acceptă mai multe moșteniri și metode abstracte (cum ar fi, de exemplu, C++ ), atunci nu este nevoie să introduceți un concept separat de „interfață” în sintaxa limbajului. Aceste entități sunt descrise folosind clase abstracte și sunt moștenite de clase pentru a implementa metode abstracte.

Totuși, susținerea moștenirii multiple în întregime este destul de complexă și provoacă multe probleme, atât la nivel de implementare a limbajului, cât și la nivelul arhitecturii aplicației. Introducerea conceptului de interfețe este un compromis care vă permite să obțineți multe dintre beneficiile moștenirii multiple (în special, capacitatea de a defini convenabil seturi de metode legate logic ca entități asemănătoare clasei care permit moștenirea și implementarea), fără a implementa ea în totalitate și astfel fără a întâmpina majoritatea dificultăților asociate cu acesta.

La nivel de execuție, schema clasică a moștenirii multiple provoacă un număr suplimentar de inconveniente:

Folosirea unei scheme cu interfețe (în loc de moștenire multiplă) evită aceste probleme, cu excepția problemei apelării metodelor de interfață (adică apelurilor de metode virtuale în moștenirea multiplă, vezi mai sus). Soluția clasică este (de exemplu, în JVM pentru Java sau CLR pentru C#) că metodele de interfață sunt apelate într-un mod mai puțin eficient, fără ajutorul unui tabel virtual: la fiecare apel, se determină mai întâi o anumită clasă de obiecte, iar apoi se caută în el metoda dorită (desigur, cu numeroase optimizări).

Implementări multiple de moștenire și interfață

De obicei, limbajele de programare permit ca o interfață să fie moștenită de la mai multe interfețe strămoși. Toate metodele declarate în interfețele strămoșilor devin parte din declarația interfeței copil. Spre deosebire de moștenirea de clasă, moștenirea multiplă a interfețelor este mult mai ușor de implementat și nu provoacă dificultăți semnificative.

Cu toate acestea, o singură coliziune cu moștenirea multiplă a interfețelor și cu implementarea mai multor interfețe de către o clasă este încă posibilă. Apare atunci când două sau mai multe interfețe moștenite de o interfață nouă sau implementate de o clasă au metode cu aceeași semnătură. Dezvoltatorii de limbaje de programare sunt nevoiți să aleagă pentru astfel de cazuri anumite metode de rezolvare a contradicțiilor. Există mai multe opțiuni aici: o interdicție a implementării, o indicație explicită a uneia specifice și o implementare a interfeței sau clasei de bază.

Interfețe în limbi și sisteme specifice

Implementarea interfețelor este determinată în mare măsură de capacitățile inițiale ale limbajului și de scopul pentru care interfețele sunt introduse în acesta. Caracteristicile utilizării interfețelor în Java , Object Pascal , Delphi și C++ sunt foarte orientative , deoarece ele demonstrează trei situații fundamental diferite: orientarea inițială a limbajului pentru a utiliza conceptul de interfețe, utilizarea lor pentru compatibilitate și emularea lor pe clase.

Delphi

Interfețele au fost introduse în Delphi pentru a sprijini tehnologia COM a Microsoft . Cu toate acestea, când a fost lansat Kylix , interfețele ca element al limbajului au fost decuplate de tehnologia COM. Toate interfețele moștenesc de la interfața [1] , care pe platforma win32 este aceeași cu interfața standard COM cu același nume, la fel cum toate clasele din ea sunt descendente ale clasei . Utilizarea explicită a lui IUnknown ca strămoș este rezervată codului care utilizează tehnologia COM. IInterface IUnknownTObject

Exemplu de declarație de interfață:

IMyInterface = procedura de interfață DoSomething ; sfârşitul ;

Pentru a declara implementarea interfețelor, în descrierea clasei, trebuie să specificați numele acestora între paranteze după cuvântul cheie class, după numele clasei strămoși. Deoarece „o interfață este un contract care trebuie îndeplinit”, programul nu se compilează până când nu este implementat în clasa de implementareprocedure DoSomething;

Concentrarea menționată mai sus a interfețelor Delphi asupra tehnologiei COM a dus la unele inconveniente. Cert este că interfața IInterface(de la care sunt moștenite toate celelalte interfețe) conține deja trei metode care sunt obligatorii pentru interfețele COM: QueryInterface, _AddRef, _Release. Prin urmare, orice clasă care implementează orice interfață trebuie să implementeze aceste metode, chiar dacă, conform logicii programului, interfața și clasa nu au nicio legătură cu COM. Trebuie remarcat faptul că aceste trei metode sunt folosite și pentru a controla durata de viață a unui obiect și pentru a implementa mecanismul de solicitare a interfeței prin asoperatorul „ ”.

Un exemplu de clasă care implementează o interfață:

TMyClass = procedura de clasa ( TMyParentClass , IMyInterface ) DoSomething ; funcția QueryInterface ( const IID : TGUID ; out Obj ) : HResult ; stdcall ; function _AddRef : Integer ; stdcall ; function _Release : Integer ; stdcall ; sfârşitul ; implementare

Programatorul trebuie să implementeze corect metodele QueryInterface, _AddRef, _Release. Pentru a scăpa de necesitatea de a scrie metode standard, este furnizată o clasă de bibliotecă TInterfacedObject - implementează cele trei metode de mai sus și orice clasă care moștenește de la ea și descendenții săi primește această implementare. Implementarea acestor metode în TInterfacedObjectpresupune control automat asupra duratei de viață a obiectului prin numărarea referințelor prin metodele _AddRefși _Release, care sunt apelate automat la intrarea și ieșirea din domeniu.

Un exemplu de clasă - moștenitor TInterfacedObject:

TMyClass = procedura de clasă ( TInterfacedObject , IMyInterface ) DoSomething ; sfârşitul ;

La moștenirea unei clase care implementează o interfață dintr-o clasă fără interfețe, programatorul trebuie să implementeze metodele menționate manual, determinând prezența sau absența controlului numărării referințelor, precum și obținerea interfeței în QueryInterface.

Un exemplu de clasă arbitrară fără numărarea referințelor:

TMyClass = clasă ( TObject , IInterface , IMyInterface ) // IInterface funcția QueryInterface ( const IID : TGUID ; out Obj ) : HResult ; stdcall ; function _AddRef : Integer ; stdcall ; function _Release : Integer ; stdcall ; // procedura IMyInterface DoSomething ; sfârşitul ; { TMyClass } funcția TMyClass . QueryInterface ( const IID : TGUID ; out Obj ) : HResult ; începe dacă GetInterface ( IID , Obj ) apoi Rezultat := 0 else Rezultat := E_NOINTERFACE ; sfârşitul ; funcția TMyClass . _AddRef : Integer ; începe Rezultatul := - 1 ; sfârşitul ; funcția TMyClass . _Release : Integer ; începe Rezultatul := - 1 ; sfârşitul ; procedura TMyClass . Fă ceva ; începe //Fă ceva sfârșit ;

C++

C++ acceptă moștenire multiple și clase abstracte , așa că, așa cum am menționat mai sus, nu este necesară o construcție sintactică separată pentru interfețele în acest limbaj. Interfețele sunt definite folosind clase abstracte , iar implementarea unei interfețe se face prin moștenirea din aceste clase.

Exemplu de definire a interfeței :

/** * interfață.Openable.h * */ #ifndef INTERFACE_OPENABLE_HPP #define INTERFACE_OPENABLE_HPP // Clasa de interfață iOpenable. Stabilește dacă ceva poate fi deschis/închis. clasa iOpenable { public : virtual ~ iOpenable (){} gol virtual deschis () = 0 ; vid virtual close () = 0 ; }; #endif

O interfață este implementată prin moștenire (datorită prezenței moștenirii multiple , este posibil să se implementeze mai multe interfețe într-o singură clasă , dacă este necesar; în exemplul de mai jos, moștenirea nu este multiplă):

/** * clasa.Ușa.h * */ #include „interface.openable.h” #include <iostream> Class Door : public iOpenable { public : Ușă (){ std :: cout << „Obiect ușă creat” << std :: endl ;} virtual ~ Ușă (){} //Incrementarea metodelor de interfață iOpenable pentru clasa Door virtual void open (){ std :: cout << "Door opened" << std :: endl ;} virtual void close (){ std :: cout << "Ușa închisă" << std :: endl ;} //Proprietăți și metode specifice clasei de ușă std :: string mMaterial ; std :: string mColor ; //... }; /** * class.Book.h ** */ #include „interface.openable.h” #include <iostream> Class Book : public iOpenable { public : Book (){ std :: cout << „Obiect carte creat” << std :: endl ;} virtual ~ Carte (){} //Incrementarea metodelor de interfață iOpenable pentru clasa Book virtual void open (){ std :: cout << "Book opened" << std :: endl ;} virtual void close (){ std :: cout << "Carte închisă" << std :: endl ;} //Proprietăți și metode specifice cărții std :: string mTitle ; std :: string mAuthor ; //... };

Să testăm totul împreună:

/** * test.openable.cpp * */ #include „interface.openable.h” #include „class.Door.h” #include „class.book.h” //Funcția de deschidere/închidere a oricăror obiecte eterogene care implementează interfața iOpenable void openAndCloseSomething ( iOpenable & smth ) { ceva . deschis (); ceva . închide (); } int main () { Door myDoor ; BookmyBook ; _ openAndCloseSomething ( ușa mea ); openAndCloseSomething ( cartea mea ); sistem ( „pauză” ); returnează 0 ; }

Java

Spre deosebire de C++, Java nu vă permite să moșteniți mai mult de o clasă. Ca alternativă la moștenirea multiplă, există interfețe. Fiecare clasă din Java poate implementa orice set de interfețe. Nu este posibil să derivați obiecte din interfețe în Java.

Declarații de interfață

O declarație de interfață este foarte asemănătoare cu o declarație de clasă simplificată.

Începe cu un titlu. Modificatorii sunt enumerați mai întâi . O interfață poate fi declarată ca public, caz în care este disponibilă pentru uz public, sau un modificator de acces poate fi omis, caz în care interfața este disponibilă numai pentru tipurile din . Nu este necesar un modificator de interfață abstractdeoarece toate interfețele sunt clase abstracte . Poate fi specificat, dar nu este recomandat să faceți acest lucru pentru a nu aglomera .

Apoi, interfacesunt scrise cuvintele cheie și numele interfeței.

Acesta poate fi urmat de un cuvânt cheie extendsși de o listă de interfețe de pe care interfața declarată va moșteni . Pot exista multe tipuri de părinți (clase și/sau interfețe) - principalul lucru este că nu există repetări și că relația de moștenire nu formează o dependență ciclică.

Moștenirea interfeței este într-adevăr foarte flexibilă. Deci, dacă există două interfețe, Ași B, și Beste moștenit de la A, atunci noua interfață Cpoate fi moștenită de la ambele. Cu toate acestea, este clar că atunci când moșteniți de la B, indicarea moștenirii de la Aeste redundantă, deoarece toate elementele acestei interfețe vor fi deja moștenite prin interfața B.

Apoi corpul interfeței este scris între paranteze.

Exemplu de declarare a interfeței (Eroare dacă clasele Colorable și Resizable: Tipul Colorable și Resizable nu poate fi o superinterfață a Drawable; o superinterfață trebuie să fie o interfață):

interfață publică Drawable se extinde Colorabil , Redimensionabil { }

Corpul interfeței este format din declararea elementelor, adică câmpuri - constante și metode abstracte . Toate câmpurile interfeței sunt automat public final static, astfel încât acești modificatori sunt opționali și chiar nedoriți pentru a nu aglomera codul. Deoarece câmpurile sunt finale, acestea trebuie inițializate imediat .

interfață publică Direcții { int DREAPTA = 1 ; int LEFT = 2 ; int UP = 3 ; int DOWN = 4 ; }

Toate metodele de interfață sunt public abstract, iar acești modificatori sunt, de asemenea, opționali.

interfață publică Moveable { void moveRight (); void moveLeft (); void moveUp (); void moveDown (); }

După cum puteți vedea, descrierea interfeței este mult mai simplă decât declarația clasei.

Implementarea interfeței

Pentru a implementa o interfață, aceasta trebuie specificată în declarația de clasă folosind implements. Exemplu:

interfață I { void interfaceMethod (); } clasă publică ImplementingInterface implementează I { void interfaceMethod () { System . afară . println ( "Această metodă este implementată de la interfața I" ); } } public static void main ( String [] args ) { ImplementingInterface temp = new ImplementingInterface (); temp . interfaceMethod (); }

Fiecare clasă poate implementa orice interfață disponibilă. În același timp, toate metodele abstracte care au apărut la moștenirea de la interfețe sau o clasă părinte trebuie implementate în clasă pentru ca noua clasă să poată fi declarată non-abstractă.

Dacă metodele cu aceeași semnătură sunt moștenite din surse diferite , atunci este suficient să descrieți implementarea o singură dată și va fi aplicată tuturor acestor metode. Cu toate acestea, dacă au o valoare de returnare diferită, atunci apare un conflict. Exemplu:

interfață A { int getValue (); } interfata B { double getValue (); } interfață C { int getValue (); } public class Corect implementează A , C // clasa moștenește corect metode cu aceeași semnătură { int getValue () { return 5 ; } } class Implementă greșită A , B // clasa aruncă o eroare de compilare { int getValue () { return 5 ; } double getValue () { return 5.5 ; } }

C#

În C# , interfețele pot moșteni de la una sau mai multe alte interfețe. Membrii interfeței pot fi metode, proprietăți, evenimente și indexatori:

interfata I1 { void Metoda1 (); } interfață I2 { void Method2 (); } interfata I : I1 , I2 { void Metoda (); int Count { get ; } event EventHandler SomeEvent ; string this [ int index ] { get ; set ; } }

Când implementează o interfață, o clasă trebuie să implementeze atât metodele interfeței în sine, cât și interfețele sale de bază:

public class C : I { public void Metoda () { } public int Count { get { throw new NotImplementedException (); } } eveniment public EventHandler SomeEvent ; public string this [ int index ] { get { throw new NotImplementedException (); } set { throw new NotImplementedException (); } } public void Metoda1 () { } public void Metoda 2 () { } }

Interfețe în UML

Interfețele în UML sunt folosite pentru a vizualiza, specifica, construi și documenta nodurile de andocare UML între părțile componente ale unui sistem. Tipurile și rolurile UML oferă un mecanism pentru modelarea mapării statice și dinamice a unei abstracții la o interfață într-un anumit context.

În UML, interfețele sunt descrise ca clase cu stereotipul „interfață” sau ca cercuri (în acest caz, operațiunile UML conținute în interfață nu sunt afișate).

Vezi și

Note

Link -uri