book-openInterfețe

Moștenirea oferă reutilizare de cod: clasa derivată primește automat proprietățile și metodele clasei de bază. Dar uneori nu avem nevoie de reutilizare de cod, avem nevoie de o garanție: certitudinea că un anumit obiect, indiferent de tipul său concret, pune la dispoziție o operație specifică.

O interfață exprimă exact această garanție. Este un contract: declară ce metode trebuie să existe, fără să spună nimic despre cum sunt implementate. Orice clasă care semnează contractul (adică îl implementează) se angajează să furnizeze toate metodele declarate. Compilatorul verifică respectarea acestui angajament.

O analogie concretă: o priză electrică este un contract. Nu îi pasă ce aparat îi conectezi, atâta timp cât aparatul respectă formatul prizei. Aparatul nu trebuie să știe cum funcționează rețeaua din spatele prizei. Ambele părți respectă contractul și interacțiunea funcționează. Interfața joacă rolul prizei; clasa care o implementează este aparatul.

Sintaxa și declararea

O interfață se declară cu interface și conține exclusiv semnăturile metodelor, fără corp, fără câmpuri, fără modificatori de acces pe membri:

interface IPretCalculabil
{
    double CalculeazaPretFinal();
    double GetReducere();
}

interface IValidabil
{
    bool EsteValid();
}

Nu există public, private sau virtual pe metodele dintr-o interfață. Prin convenție, toate sunt publice și trebuie implementate. Numele interfețelor începe prin convenție cu litera I, semnalând explicit că este vorba de un contract, nu de o clasă.

Implementarea unei interfețe

O clasă declară că respectă contractul cu același : folosit la moștenire, urmat de numele interfeței. Poate implementa oricâte interfețe, separate prin virgulă:

Dacă o clasă declară că implementează o interfață dar nu definește toate metodele din ea, codul nu compilează. Compilatorul verifică că fiecare metodă din contract are o implementare. Nu există nicio cale de a „uita" să implementezi o metodă din interfață.

Interfață față de moștenire

Moștenirea și interfețele utilizează același simbol :, dar rezolvă probleme diferite.

Moștenirea exprimă relația „este un" și aduce cod reutilizabil din clasa de bază. BiletStudent moștenește Bilet și primește automat toate câmpurile și metodele acestuia.

Interfața exprimă relația „poate face" și impune un contract fără a contribui cu nicio linie de cod. Bilet implementează IPretCalculabil și garantează că știe să calculeze un preț, dar interfața nu furnizează nicio implementare.

Diferența practică cea mai importantă: în C# poți moșteni o singură clasă, dar poți implementa oricâte interfețe:

Dacă Bilet ar trebui să moștenească simultan din două clase diferite, nu ar fi posibil. Dar dacă Bilet trebuie să respecte mai multe contracte, le poate implementa pe toate.

Utilitatea contractului comun

Valoarea reală a interfețelor apare atunci când vrei să tratezi uniform obiecte care nu sunt legate prin moștenire, sau când vrei să decuplezi codul de tipurile concrete cu care lucrează.

b1, b2 și b3 pot fi de orice tip, atâta timp cât implementează IPretCalculabil. Codul care calculează totalul nu importă și nu cunoaște tipurile concrete. Dacă adaugi mâine BiletCopil care implementează IPretCalculabil, același foreach funcționează fără nicio modificare.

Același principiu face ca CasaBilete să poată lucra cu orice tip de bilet viitor, nu doar cu cele existente la momentul scrierii sale.

O interfață poate fi implementată de clase fără legătură

Spre deosebire de moștenire, interfața nu impune o relație ierarhică. Clase complet diferite, fără nicio legătură structurală, pot implementa aceeași interfață și pot fi tratate uniform prin tipul ei:

Bilet, ContUtilizator și CupoanReducere nu au nicio legătură prin moștenire. Dar toate pot fi validate și pot fi tratate uniform prin tipul IValidabil:

Codul de validare funcționează identic pentru toate tipurile, fără să știe nimic despre implementările concrete.

Interfețele și clasele derivate

O clasă derivată moștenește și implementările de interfețe ale clasei de bază. BiletStudent moștenește Bilet, deci implementează automat IPretCalculabil și IValidabil și nu este nevoie să le declare explicit din nou.

Dacă BiletStudent suprascrie metodele cu override, versiunile suprascrise sunt cele apelate atunci când accesul se face prin tipul interfeței. Polimorfismul funcționează la fel, indiferent dacă variabila este de tipul clasei de bază sau de tipul interfeței:

Interfețele nu schimbă regulile de polimorfism, ci le extind la un nivel suplimentar de abstractizare.

Last updated