En tancar la lliçó anterior va quedar pendent un problema: Llibre ja té Titol, Autor, Disponible, Prestar(), Retornar() i MostrarFitxa() perfectament definits, però si BiblioTech volgués incorporar també revistes al catàleg, hauries de crear una classe Revista copiant i enganxant exactament aquestes mateixes propietats i mètodes, canviant només allò específic d'una revista (per exemple, el seu número d'edició). Aquesta duplicació és precisament el que la herència evita: permet definir una classe base amb allò que diversos tipus tenen en comú, i que altres classes hereten automàticament aquestes propietats i mètodes, afegint només allò que les distingeix. En aquesta lliçó extrauràs de Llibre una classe base, MaterialBibliotecari, de la qual heretaran tant Llibre com una nova classe Revista.

Contingut

  1. El problema: propietats i mètodes duplicats
  2. Heretar amb : — la classe base MaterialBibliotecari
  3. Llibre hereta de MaterialBibliotecari
  4. L'operador base per cridar el constructor de la classe base
  5. Afegint Revista com a segona classe hereva
  6. Sobreescriure membres heretats amb virtual i override
  7. Classes segellades amb sealed
  8. Jerarquia d'una sola herència en C#

  1. El problema: propietats i mètodes duplicats

Recupera la classe Llibre tal com va quedar a la lliçó anterior:

class Llibre
{
    public string Titol { get; set; }
    public string Autor { get; set; }
    public string Isbn { get; set; }
    public bool Disponible { get; set; } = true;

    public Llibre(string titol, string autor, string isbn)
    {
        Titol = titol;
        Autor = autor;
        Isbn = isbn;
    }

    public void Prestar() { /* ... */ }
    public void Retornar() { /* ... */ }
    public void MostrarFitxa() { /* ... */ }
}

Si ara escrivissis una classe Revista des de zero, amb Titol, Autor, Disponible, Prestar(), Retornar() i MostrarFitxa() pràcticament idèntics, i només Isbn substituït per, per exemple, NumeroEdicio, estaries duplicant una quantitat considerable de codi. I el que és pitjor: si més endavant corregeixes un error a Prestar(), hauries de recordar corregir-lo també a la còpia de Revista. L'herència resol aquest problema d'arrel.

  1. Heretar amb : — la classe base MaterialBibliotecari

El primer pas és identificar què tenen en comú un llibre i una revista dins de BiblioTech: tots dos tenen un títol, un autor, i poden estar disponibles o prestats. Aquesta part comuna s'extreu a una nova classe, MaterialBibliotecari, que actuarà com a classe base:

class MaterialBibliotecari
{
    public string Titol { get; set; }
    public string Autor { get; set; }
    public bool Disponible { get; 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 void MostrarFitxa()
    {
        Console.WriteLine($"Titol: {Titol}");
        Console.WriteLine($"Autor: {Autor}");
        Console.WriteLine($"Disponible: {Disponible}");
    }
}

MaterialBibliotecari no és un concepte que s'utilitzi "en solitari" a BiblioTech —mai crearàs un objecte que sigui "un material bibliotecari" a seques, sense més—, sinó la base sobre la qual es construeixen conceptes més concrets com Llibre o Revista. Aquesta idea es retomarà a la lliçó d'Abstracció, més endavant en el mòdul.

  1. Llibre hereta de MaterialBibliotecari

Perquè una classe hereti d'una altra, s'escriuen dos punts (:) després del nom de la classe, seguits del nom de la classe base:

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

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

class Llibre : MaterialBibliotecari significa "Llibre és un MaterialBibliotecari, a més de tenir les seves pròpies particularitats". Gràcies a l'herència, Llibre obté automàticament totes les propietats (Titol, Autor, Disponible) i mètodes (Prestar(), Retornar(), MostrarFitxa()) de MaterialBibliotecari, sense tornar-los a escriure, i només necessita declarar allò que li és propi: Isbn.

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

Console.WriteLine(llibre1.Titol);     // Rayuela (heretat de MaterialBibliotecari)
Console.WriteLine(llibre1.Isbn);      // 978-84-376-0495-4 (propi de Llibre)
llibre1.Prestar();                    // 'Rayuela' ha estat prestat. (heretat)
llibre1.MostrarFitxa();               // heretat, mostra Titol/Autor/Disponible

  1. L'operador base per cridar el constructor de la classe base

Al constructor de Llibre de l'apartat anterior apareix : base(titol, autor) després de la signatura del constructor. La paraula clau base permet invocar explícitament un membre de la classe base —en aquest cas, el seu constructor— des de la classe hereva.

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

Això és necessari perquè MaterialBibliotecari exigeix titol i autor en el seu propi constructor (no té un constructor sense paràmetres), així que qualsevol classe que hereti d'ella ha d'indicar explícitament com es construeix aquesta part heretada. L'ordre d'execució és sempre: primer s'executa el constructor de la classe base (MaterialBibliotecari(titol, autor), que assigna Titol i Autor), i després el cos del constructor de Llibre (que assigna Isbn).

base també es pot fer servir dins d'un mètode per invocar la versió de la classe base d'aquest mateix mètode (ho veuràs a l'apartat 6, amb MostrarFitxa()), no només en constructors.

  1. Afegint Revista com a segona classe hereva

Amb MaterialBibliotecari ja definida, afegir Revista al catàleg de BiblioTech és ara molt més senzill: només cal allò que la distingeix d'un llibre, que en aquest cas serà el seu número d'edició.

class Revista : MaterialBibliotecari
{
    public int NumeroEdicio { get; set; }

    public Revista(string titol, string autor, int numeroEdicio) : base(titol, autor)
    {
        NumeroEdicio = numeroEdicio;
    }
}
Revista revista1 = new Revista("National Geographic", "Diversos autors", 302);

revista1.MostrarFitxa();  // heretat: Titol, Autor, Disponible
Console.WriteLine(revista1.NumeroEdicio); // 302 (propi de Revista)
revista1.Prestar();       // heretat, funciona igual que a Llibre

Llibre i Revista comparteixen tota la lògica de Titol, Autor, Disponible, Prestar() i Retornar() sense haver-la escrita dues vegades: totes dues hereten de la mateixa classe base MaterialBibliotecari, i cadascuna afegeix únicament la seva particularitat (Isbn en un cas, NumeroEdicio en l'altre).

  1. Sobreescriure membres heretats amb virtual i override

Per defecte, un mètode heretat es comporta exactament igual a la classe hereva que a la classe base: llibre1.MostrarFitxa() i revista1.MostrarFitxa() executen literalment el mateix codi de MaterialBibliotecari. Però té sentit que la fitxa d'un llibre inclogui també el seu ISBN, i la d'una revista, el seu número d'edició —informació que MaterialBibliotecari no coneix. Per permetre que una classe hereva sobreescrigui el comportament d'un mètode heretat, la classe base ha de marcar aquest mètode com a virtual, i la hereva ha de marcar la seva pròpia versió com a override:

class MaterialBibliotecari
{
    // ... propietats i constructor com abans ...

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

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

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

    public override void MostrarFitxa()
    {
        base.MostrarFitxa();               // executa primer la versio de MaterialBibliotecari
        Console.WriteLine($"ISBN: {Isbn}"); // i afegeix la linia propia de Llibre
    }
}
Llibre llibre1 = new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4");
llibre1.MostrarFitxa();
// Titol: Rayuela
// Autor: Julio Cortazar
// Disponible: True
// ISBN: 978-84-376-0495-4

Dins de Llibre.MostrarFitxa(), la crida base.MostrarFitxa() executa la versió original del mètode definida a MaterialBibliotecari (les tres primeres línies), i a continuació s'afegeix la línia específica de l'ISBN. Això evita duplicar les línies de Titol, Autor i Disponible: es reutilitza la implementació de la classe base i només s'afegeix el que és nou. Revista faria exactament el mateix amb el seu propi NumeroEdicio:

class Revista : MaterialBibliotecari
{
    public int NumeroEdicio { get; set; }

    public Revista(string titol, string autor, int numeroEdicio) : base(titol, autor)
    {
        NumeroEdicio = numeroEdicio;
    }

    public override void MostrarFitxa()
    {
        base.MostrarFitxa();
        Console.WriteLine($"Numero d'edicio: {NumeroEdicio}");
    }
}
Paraula clau On s'utilitza Què significa
virtual Al mètode de la classe base "Les classes hereves poden sobreescriure aquest mètode"
override Al mètode de la classe hereva "Aquesta és una versió pròpia d'un mètode marcat virtual a la base"
base.Metode() Dins d'un mètode sobreescrit "Executa també la versió original del mètode, definida a la classe base"

Aquesta lliçó s'ha centrat en la mecànica d'heretar i sobreescriure; a la propera lliçó, Polimorfisme, veuràs per què aquesta capacitat de sobreescriure mètodes és molt més potent del que sembla a primera vista, quan es combina amb llistes i arrays de la classe base.

  1. Classes segellades amb sealed

A vegades convé impedir explícitament que una classe tingui hereves, per exemple perquè el seu disseny no està pensat per estendre's més. Per a això s'utilitza la paraula clau sealed abans de class:

sealed class Revista : MaterialBibliotecari
{
    public int NumeroEdicio { get; set; }

    public Revista(string titol, string autor, int numeroEdicio) : base(titol, autor)
    {
        NumeroEdicio = numeroEdicio;
    }

    public override void MostrarFitxa()
    {
        base.MostrarFitxa();
        Console.WriteLine($"Numero d'edicio: {NumeroEdicio}");
    }
}

// class RevistaDigital : Revista { }
// Error de compilacio: no es pot heretar d'una classe segellada ('sealed')

Marcar Revista com a sealed deixa clar, per a qui llegeixi el codi, que no està pensada com una nova classe base: és un punt final en aquesta branca de la jerarquia. MaterialBibliotecari i Llibre, en canvi, es mantenen sense sealed durant la resta del mòdul, ja que MaterialBibliotecari necessita continuar sent hereva per disseny.

  1. Jerarquia d'una sola herència en C#

Una limitació important —i deliberada— de C# és que una classe només pot heretar d'una classe base: no existeix l'herència múltiple de classes, a diferència d'altres aspectes del llenguatge (com les interfícies, que sí que es poden combinar diverses alhora, i que veuràs al Mòdul 4). Això significa que la jerarquia d'herència de BiblioTech forma sempre un arbre, mai una xarxa:

classDiagram
    MaterialBibliotecari <|-- Llibre
    MaterialBibliotecari <|-- Revista
    class MaterialBibliotecari {
        +string Titol
        +string Autor
        +bool Disponible
        +Prestar()
        +Retornar()
        +MostrarFitxa()
    }
    class Llibre {
        +string Isbn
        +MostrarFitxa()
    }
    class Revista {
        +int NumeroEdicio
        +MostrarFitxa()
    }

Totes les classes en C# —incloent MaterialBibliotecari, encara que no ho hagis escrit explícitament— hereten, en última instància, d'una classe arrel comuna anomenada object, de la qual provenen mètodes com ToString() que ja has fet servir indirectament en interpolar objectes dins de cadenes de text. Això queda fora de l'abast d'aquesta lliçó, però convé saber que la jerarquia de classes de C# sempre té, al cim, aquesta arrel comuna.

Errors Comuns i Consells

  • Oblidar base(...) quan la classe base no té constructor sense paràmetres: si MaterialBibliotecari només defineix MaterialBibliotecari(string titol, string autor), qualsevol classe hereva està obligada a indicar : base(...) amb aquests arguments; si no, el compilador dona un error.
  • Sobreescriure sense override (o sense virtual a la base): si el mètode de la classe base no és virtual, la classe hereva no pot fer servir override; i si el que es busca és amagar el mètode amb un altre nom sense relació real de sobreescriptura (usant new a la signatura del mètode), el comportament resultant sol confondre i rarament és el que es pretenia. Fes servir sempre virtual/override quan la intenció sigui sobreescriure.
  • Duplicar codi que ja és a la classe base: si Llibre i Revista tornen a definir Titol, Autor o Disponible pel seu compte, es perd l'avantatge de l'herència i es reintrodueix el problema que aquesta lliçó resol.
  • Marcar sealed una classe que sí necessites continuar estenent: sealed és una decisió de disseny deliberada; aplica-la només quan tinguis la certesa que aquesta classe no ha de tenir més hereves (com Revista en aquest mòdul), no per costum.
  • Esperar herència múltiple de classes: en C# una classe només pot heretar d'una única classe base; si necessites combinar diverses capacitats independents, l'eina adequada són les interfícies, que veuràs al Mòdul 4.

Exercicis

  1. Defineix la classe MaterialBibliotecari amb les propietats Titol i Autor (string) i Disponible (bool, amb inicialitzador de propietat a true), un constructor MaterialBibliotecari(string titol, string autor), i un mètode void MostrarFitxa() que mostri les tres propietats per consola.

  2. Crea la classe Llibre : MaterialBibliotecari amb la propietat addicional Isbn (string) i un constructor Llibre(string titol, string autor, string isbn) que faci servir base(...) per inicialitzar la part heretada. Crea un objecte Llibre i mostra el seu Titol (heretat) i el seu Isbn (propi).

  3. Crea la classe Revista : MaterialBibliotecari (sense sealed per ara) amb la propietat NumeroEdicio (int) i el seu propi constructor amb base(...). Marca MostrarFitxa() com a virtual a MaterialBibliotecari i sobreescriu-lo a Revista amb override, de manera que, a més de la fitxa heretada, mostri també el número d'edició fent servir base.MostrarFitxa().

Solucions

class MaterialBibliotecari
{
    public string Titol { get; set; }
    public string Autor { get; set; }
    public bool Disponible { get; set; } = true;

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

    public void MostrarFitxa()
    {
        Console.WriteLine($"Titol: {Titol}");
        Console.WriteLine($"Autor: {Autor}");
        Console.WriteLine($"Disponible: {Disponible}");
    }
}
class Llibre : MaterialBibliotecari
{
    public string Isbn { get; set; }

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

Llibre llibre1 = new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4");
Console.WriteLine(llibre1.Titol); // Rayuela
Console.WriteLine(llibre1.Isbn);  // 978-84-376-0495-4
class MaterialBibliotecari
{
    public string Titol { get; set; }
    public string Autor { get; set; }
    public bool Disponible { get; set; } = true;

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

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

class Revista : MaterialBibliotecari
{
    public int NumeroEdicio { get; set; }

    public Revista(string titol, string autor, int numeroEdicio) : base(titol, autor)
    {
        NumeroEdicio = numeroEdicio;
    }

    public override void MostrarFitxa()
    {
        base.MostrarFitxa();
        Console.WriteLine($"Numero d'edicio: {NumeroEdicio}");
    }
}

Revista revista1 = new Revista("National Geographic", "Diversos autors", 302);
revista1.MostrarFitxa();
// Titol: National Geographic
// Autor: Diversos autors
// Disponible: True
// Numero d'edicio: 302

Conclusió

En aquesta lliçó has après a evitar la duplicació de codi extraient una classe base, MaterialBibliotecari, de la qual hereten Llibre i la nova Revista mitjançant :; has fet servir base per invocar el constructor (i el mètode) de la classe base, has sobreescrit membres heretats amb virtual/override, has vist com sealed impedeix que una classe tingui hereves, i per què C# només permet heretar d'una única classe base alhora. El model de domini de BiblioTech ara té una jerarquia real: MaterialBibliotecari a la base, amb Llibre i Revista com a especialitzacions concretes.

Tanmateix, encara no has explotat la veritable potència d'aquesta jerarquia: poder tractar un Llibre i una Revista de forma uniforme, com si tots dos fossin simplement "un MaterialBibliotecari", i deixar que cadascun es comporti segons el seu propi tipus en temps d'execució. Això és exactament el polimorfisme, el tema de la propera lliçó.

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