El Mòdul 3 va acabar amb una promesa pendent: a la lliçó d'Abstracció vas veure que abstract class i interface són "dues eines diferents per expressar abstracció", i que les interfícies s'estudiarien en detall en aquest mòdul. Ha arribat el moment. Una interfície és un contracte: una llista de membres (mètodes, propietats, esdeveniments) que una classe es compromet a implementar, sense que la interfície mateixa aporti cap codi. Aquesta lliçó obre el Mòdul 4 de Conceptes Avançats definint dues interfícies per a BiblioTech —IPrestable i ICercable— i aplicant-les sobre el model de domini que ja coneixes, com a primer pas cap a la resta d'eines modernes de C# que veuràs en aquest mòdul.

Contingut

  1. Què és una interfície i quin problema resol
  2. Interfícies enfront de classes abstractes: recuperant la distinció del Mòdul 3
  3. Definir i implementar una interfície: IPrestable
  4. Implementació múltiple d'interfícies
  5. Una segona interfície: ICercable
  6. Membres amb implementació per defecte (C# 8 en endavant)
  7. El model de BiblioTech amb interfícies aplicades

  1. Què és una interfície i quin problema resol

Una interfície declara què ha de poder fer un tipus, sense dir com ho fa. Es defineix amb la paraula clau interface, per convenció amb el prefix I (IPrestable, ICercable, o les ja conegudes IEnumerable, IComparable de la mateixa biblioteca de .NET), i enumera membres sense cap cos d'implementació:

interface IPrestable
{
    bool Disponible { get; }
    void Prestar();
    void Retornar();
}

Qualsevol classe que declari : IPrestable es compromet, davant el compilador, a oferir una propietat Disponible (almenys amb lectura), i els mètodes Prestar() i Retornar() amb exactament aquestes signatures. Si en falta algun, el codi no compila. La interfície en si mateixa no es pot instanciar (new IPrestable() no té sentit i no compila) ni conté cap camp amb estat propi: és pura forma, cap contingut.

El problema que resol és el de desacoblar el contracte de la implementació: codi que només necessita saber que alguna cosa "es pot prestar" pot treballar amb la interfície IPrestable sense importar-li si per sota hi ha un Llibre, una Revista o qualsevol tipus futur que ni tan sols existeixi encara, sempre que compleixi el contracte.

  1. Interfícies enfront de classes abstractes: recuperant la distinció del Mòdul 3

A la lliçó d'Abstracció vas veure que una classe abstracta modela una relació "és un" amb herència de codi compartit, mentre que una interfície modela un contracte de "pot fer" sense cap implementació compartida. Amb les interfícies ja definides formalment, aquesta taula recull la comparació completa:

Classe abstracta (abstract class) Interfície (interface)
Relació que modela "És un" (Llibre és un MaterialBibliotecari) "Pot fer" (Llibre es pot prestar)
Implementació compartida Sí, en els membres no abstract No, tret de membres per defecte (apartat 6)
Constructor propi No
Camps amb estat No
Quantes en pot combinar una classe Una única classe base Diverses interfícies alhora
Exemple a BiblioTech MaterialBibliotecari IPrestable, ICercable

La regla pràctica per decidir entre totes dues: si necessites compartir codi real (propietats ja implementades, mètodes amb cos) entre classes relacionades per herència, fes servir una classe abstracta; si només necessites garantir que diversos tipus —relacionats entre si o no— ofereixin certes capacitats, sense compartir cap implementació, fes servir una interfície. De fet, totes dues eines no són excloents: en el següent apartat veuràs que MaterialBibliotecari continua sent abstract class i, a més, implementa interfícies.

  1. Definir i implementar una interfície: IPrestable

Implementar una interfície consisteix a declarar-la després del nom de la classe (després de la classe base, si n'hi ha, separades per comes) i assegurar-se que tots els seus membres estan coberts per membres public amb la mateixa signatura:

abstract class MaterialBibliotecari : IPrestable
{
    public string Titol { get; set; }
    public string Autor { get; set; }
    public bool Disponible { get; private set; } = true;

    public MaterialBibliotecari(string titol, string autor)
    {
        Titol = titol;
        Autor = autor;
    }

    public void Prestar()
    {
        if (Disponible)
        {
            Disponible = false;
            Console.WriteLine($"'{Titol}' ha estat prestat.");
        }
        else
        {
            Console.WriteLine($"'{Titol}' no esta disponible per a prestec.");
        }
    }

    public void Retornar()
    {
        Disponible = true;
        Console.WriteLine($"'{Titol}' ha estat retornat.");
    }

    public virtual void MostrarFitxa()
    {
        Console.WriteLine($"Titol: {Titol}");
        Console.WriteLine($"Autor: {Autor}");
        Console.WriteLine($"Disponible: {Disponible}");
    }

    public abstract string Descriure();
}

Fixa't en un detall important: MaterialBibliotecari ja tenia Disponible, Prestar() i Retornar() des del Mòdul 3, amb exactament l'aspecte que IPrestable exigeix. No va caldre escriure ni una línia de codi nova per "complir" la interfície: n'hi va haver prou d'afegir : IPrestable i el compilador comprova que els membres ja existents encaixen amb el contracte. Això és habitual: les interfícies solen formalitzar capacitats que una classe ja tenia, donant-los un nom explícit i reutilitzable.

Llibre llibre1 = new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4");

IPrestable prestable = llibre1; // valid: Llibre es-un MaterialBibliotecari, que es-un IPrestable
prestable.Prestar();
Console.WriteLine(prestable.Disponible); // False

Una variable de tipus interfície (IPrestable prestable) només pot fer servir els membres declarats a la interfície (Disponible, Prestar(), Retornar()), encara que l'objecte subjacent sigui un Llibre amb molts més membres propis (Isbn, Descriure()...); per accedir a aquests altres membres caldria convertir de nou a Llibre, amb is/as, com ja vas veure a la lliçó de Polimorfisme.

  1. Implementació múltiple d'interfícies

A diferència de l'herència de classes (una classe només pot tenir una classe base directa), una classe pot implementar diverses interfícies alhora, separades per comes:

class Exemple : ClasseBase, IPrestable, ICercable, IComparable<Exemple>
{
    // ha de complir el contracte de les tres interficies, a mes d'heretar de ClasseBase
}

Aquest és un dels avantatges pràctics més citats de les interfícies enfront de les classes abstractes: permeten combinar capacitats independents entre si (una cosa pot ser, alhora, "prestable", "cercable" i "comparable") sense forçar una única jerarquia d'herència rígida. En el següent apartat, MaterialBibliotecari passarà a implementar dues interfícies diferents alhora.

  1. Una segona interfície: ICercable

Per completar l'exemple, es defineix una segona interfície pensada per a les cerques al catàleg de BiblioTech (una capacitat que faràs servir de forma més completa a la lliçó de LINQ, més endavant en aquest mòdul):

interface ICercable
{
    bool Coincideix(string text);
}

MaterialBibliotecari implementa totes dues interfícies alhora, i declara Coincideix() com a virtual (no forma part del contracte de la interfície que un mètode hagi de ser virtual, però res ho impedeix: és una decisió de disseny perquè Llibre i Revista puguin enriquir la cerca amb les seves pròpies dades):

abstract class MaterialBibliotecari : IPrestable, ICercable
{
    // ... Titol, Autor, Disponible, constructor, Prestar(), Retornar(), MostrarFitxa() ...

    public abstract string Descriure();

    public virtual bool Coincideix(string text)
    {
        return Titol.Contains(text, StringComparison.OrdinalIgnoreCase)
            || Autor.Contains(text, StringComparison.OrdinalIgnoreCase);
    }
}

Llibre sobreescriu Coincideix() perquè la cerca també tingui en compte l'ISBN, reutilitzant la implementació de la classe base amb base.Coincideix(text):

class Llibre : MaterialBibliotecari
{
    public string Isbn { get; set; }

    public Llibre(string titol, string autor, string isbn) : base(titol, autor)
    {
        Isbn = isbn;
    }

    public override string Descriure()
    {
        return $"Llibre: {Titol}, de {Autor} (ISBN {Isbn})";
    }

    public override bool Coincideix(string text)
    {
        return base.Coincideix(text) || Isbn.Contains(text, StringComparison.OrdinalIgnoreCase);
    }
}
Llibre llibre1 = new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4");

Console.WriteLine(llibre1.Coincideix("cortazar"));        // True: coincideix en Autor
Console.WriteLine(llibre1.Coincideix("978-84-376-0495")); // True: coincideix en Isbn
Console.WriteLine(llibre1.Coincideix("harry potter"));    // False

  1. Membres amb implementació per defecte (C# 8 en endavant)

Des de C# 8, una interfície pot incloure, opcionalment, una implementació per defecte per a algun dels seus membres, fent servir el mateix cos { } (o =>) que en una classe. Qualsevol classe que implementi la interfície hereta aquest comportament automàticament, sense haver-lo d'escriure, tot i que encara pot sobreescriure'l si ho necessita:

interface IPrestable
{
    bool Disponible { get; }
    void Prestar();
    void Retornar();

    string EstatText() => Disponible ? "Disponible" : "Prestat"; // membre amb cos per defecte
}
Llibre llibre1 = new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4");
IPrestable prestable = llibre1;

Console.WriteLine(prestable.EstatText()); // "Disponible", sense que MaterialBibliotecari l'implementi

Aquesta característica es fa servir sobretot quan una biblioteca ja publicada necessita afegir un nou membre a una interfície existent sense trencar el codi de qui ja la implementava: gràcies al cos per defecte, les classes antigues continuen compilant sense canvis. En el codi d'aplicació quotidià (com el de BiblioTech) és una eina que apareix amb poca freqüència; n'hi ha prou de saber que existeix i reconèixer-la si la veus en codi d'altri.

  1. El model de BiblioTech amb interfícies aplicades

Amb els canvis d'aquest apartat, així queda MaterialBibliotecari implementant totes dues interfícies, sense que res canviï en com es fan servir Llibre i Revista des de la resta del programa:

classDiagram
    class IPrestable {
        <<interface>>
        +bool Disponible
        +Prestar()
        +Retornar()
    }
    class ICercable {
        <<interface>>
        +Coincideix(string) bool
    }
    class MaterialBibliotecari {
        <<abstract>>
        +string Titol
        +string Autor
        +Descriure()* string
    }
    class Llibre {
        +string Isbn
    }
    class Revista {
        +int NumeroEdicio
    }
    IPrestable <|.. MaterialBibliotecari
    ICercable <|.. MaterialBibliotecari
    MaterialBibliotecari <|-- Llibre
    MaterialBibliotecari <|-- Revista

La fletxa de puntets (<|..) és la notació habitual per a "implementa una interfície", diferent de la fletxa contínua (<|--) d'herència entre classes que ja coneixes del Mòdul 3.

Errors Comuns i Consells

  • Intentar donar cos a un membre d'interfície sense fer servir la sintaxi de membre per defecte: void Prestar(); acaba en ;, igual que un mètode abstract; si escrius { } directament a la interfície sense haver-ho pensat, en realitat estàs fent servir (a partir de C# 8) un membre per defecte, amb les implicacions de l'apartat 6.
  • Oblidar implementar algun membre de la interfície: si una classe declara : IPrestable però li falta, per exemple, Retornar(), el compilador dona un error assenyalant exactament quin membre falta per implementar.
  • Confondre el tipus de la variable amb el tipus real de l'objecte: IPrestable prestable = llibre1; només permet accedir als membres d'IPrestable; per fer servir Isbn o Descriure() cal convertir de nou a Llibre amb is/as.
  • Abusar d'interfícies amb un únic mètode: si una única classe implementa la interfície i no hi ha cap raó per desacoblar el contracte de la implementació, de vegades n'hi ha prou amb un mètode normal; les interfícies aporten més valor com més implementacions diferents puguin compartir el mateix contracte.
  • Consell: anomena les interfícies sempre amb el prefix I (IPrestable, ICercable, IComparable); és una convenció molt consolidada en C# i en tota la biblioteca de .NET, i facilita reconèixer d'una ullada que un tipus és una interfície.

Exercicis

  1. Defineix la interfície IPrestable amb bool Disponible { get; }, void Prestar(); i void Retornar();. Fes que MaterialBibliotecari la implementi (recorda que els seus membres ja existents haurien de cobrir el contracte sense canvis). Crea un Llibre, assigna'l a una variable IPrestable i crida Prestar() a través d'aquesta variable.

  2. Defineix la interfície ICercable amb bool Coincideix(string text);. Implementa Coincideix() a MaterialBibliotecari (comparant Titol i Autor) i sobreescriu-lo a Revista perquè també compari per NumeroEdicio.ToString(). Comprova el resultat amb diverses cerques d'exemple.

  3. Afegeix a IPrestable un membre amb implementació per defecte string EstatText() => Disponible ? "Disponible" : "Prestat";. Crea un Llibre, presta'l, i mostra el resultat de EstatText() abans i després de prestar-lo, accedint sempre a través d'una variable de tipus IPrestable.

Solucions

interface IPrestable
{
    bool Disponible { get; }
    void Prestar();
    void Retornar();
}

abstract class MaterialBibliotecari : IPrestable
{
    // Titol, Autor, Disponible, constructor, Prestar(), Retornar() sense canvis
}

Llibre llibre1 = new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4");
IPrestable prestable = llibre1;
prestable.Prestar(); // "'Rayuela' ha estat prestat."
interface ICercable
{
    bool Coincideix(string text);
}

abstract class MaterialBibliotecari : IPrestable, ICercable
{
    // ...
    public virtual bool Coincideix(string text)
    {
        return Titol.Contains(text, StringComparison.OrdinalIgnoreCase)
            || Autor.Contains(text, StringComparison.OrdinalIgnoreCase);
    }
}

class Revista : MaterialBibliotecari
{
    // ...
    public override bool Coincideix(string text)
    {
        return base.Coincideix(text) || NumeroEdicio.ToString().Contains(text);
    }
}

Revista revista1 = new Revista("National Geographic", "Varios autores", 302);
Console.WriteLine(revista1.Coincideix("302"));      // True
Console.WriteLine(revista1.Coincideix("national")); // True
interface IPrestable
{
    bool Disponible { get; }
    void Prestar();
    void Retornar();

    string EstatText() => Disponible ? "Disponible" : "Prestat";
}

Llibre llibre1 = new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4");
IPrestable prestable = llibre1;

Console.WriteLine(prestable.EstatText()); // "Disponible"
prestable.Prestar();
Console.WriteLine(prestable.EstatText()); // "Prestat"

Conclusió

En aquesta lliçó has après a definir interfícies amb interface, a implementar-les en classes existents (comprovant que MaterialBibliotecari ja complia la major part del contracte d'IPrestable), a combinar diverses interfícies en una mateixa classe, i a reconèixer els membres amb implementació per defecte de C# 8. MaterialBibliotecari queda ara com abstract class MaterialBibliotecari : IPrestable, ICercable, amb Llibre i Revista heretant i enriquint tots dos contractes.

Les interfícies resolen el "què pot fer un tipus", però hi ha una altra pregunta pendent a BiblioTech: com reaccionar quan passa alguna cosa —per exemple, quan es registra un préstec—, sense acoblar directament qui ho provoca amb qui se n'ha d'assabentar. A la propera lliçó coneixeràs els delegats i els esdeveniments, l'eina de C# pensada exactament per a això, i veuràs néixer la classe Biblioteca, que a les properes lliçons d'aquest mòdul passarà a organitzar tot el catàleg i els socis.

Curs de Programació en C#

Mòdul 1: Introducció al C#

Mòdul 2: Estructures de Control

Mòdul 3: Programació Orientada a Objectes

Mòdul 4: Conceptes Avançats de C#

Mòdul 5: Treballant amb Dades

Mòdul 6: Temes Avançats

Mòdul 7: Construcció d'Aplicacions

Mòdul 8: Bones Pràctiques i Patrons de Disseny

Mòdul 9: Projecte Final

© Copyright 2026. Tots els drets reservats