Hi ha un problema latent al model de BiblioTech tal com ha quedat fins ara: qualsevol part del programa pot escriure directament llibre1.Disponible = true;, sense passar per Prestar() ni Retornar(), deixant el catàleg en un estat incoherent (per exemple, un llibre marcat disponible que en realitat continua en mans d'un soci). L'encapsulació és el principi de la programació orientada a objectes que resol precisament això: ocultar l'estat intern d'un objecte i exposar únicament les operacions controlades a través de les quals aquest estat pot canviar. En aquesta lliçó aprendràs els modificadors d'accés de C# i tancaràs aquesta porta del darrere de Disponible, perquè només es pugui modificar mitjançant els mètodes pensats per a això.

Contingut

  1. Què és l'encapsulació i per què importa
  2. Modificadors d'accés: public, private, protected, internal
  3. El problema concret: Disponible modificable lliurement
  4. private set: exposar lectura, restringir escriptura
  5. Propietats amb lògica de validació al set
  6. Propietats de només lectura
  7. Aplicant encapsulació a la resta del model: Soci i Prestec
  8. El principi de mínima exposició

  1. Què és l'encapsulació i per què importa

L'encapsulació consisteix a ocultar els detalls interns de com un objecte emmagatzema i gestiona el seu estat, exposant cap enfora únicament una interfície controlada d'operacions vàlides. La idea central és: el codi extern a la classe no hauria de poder deixar un objecte en un estat incoherent. Fins ara, qualsevol codi amb accés a un objecte Llibre pot escriure llibre1.Disponible = false; sense haver cridat realment Prestar(), saltant-se per complet la lògica que decideix si el préstec és vàlid (per exemple, comprovar que el llibre estigués realment disponible abans de prestar-lo). L'encapsulació tanca aquesta via d'escapament.

  1. Modificadors d'accés: public, private, protected, internal

C# ofereix diversos modificadors d'accés que controlen des d'on es pot fer servir un membre (propietat, mètode, camp) d'una classe:

Modificador Accessible des de...
public Qualsevol part del programa, sense restriccions
private Únicament des de dins de la pròpia classe
protected Des de la pròpia classe i des de qualsevol classe que n'heretés
internal Des de qualsevol part del mateix assemblatge (projecte compilat), però no des d'altres projectes externs

Un exemple breu i genèric, fora del model de BiblioTech, ajuda a fixar la diferència entre private i protected:

class Base
{
    private int valorPrivat = 1;
    protected int valorProtegit = 2;
    public int valorPublic = 3;

    public void MostrarDesDeBase()
    {
        Console.WriteLine(valorPrivat);   // Ok: mateixa classe
        Console.WriteLine(valorProtegit); // Ok: mateixa classe
    }
}

class Derivada : Base
{
    public void MostrarDesDeDerivada()
    {
        // Console.WriteLine(valorPrivat);    // Error: no accessible, es private a Base
        Console.WriteLine(valorProtegit);     // Ok: protected SI es accessible des d'una classe hereva
        Console.WriteLine(valorPublic);       // Ok: public, accessible des de qualsevol lloc
    }
}

Derivada objecte = new Derivada();
Console.WriteLine(objecte.valorPublic);       // Ok: public
// Console.WriteLine(objecte.valorProtegit);  // Error: protected no es accessible des de fora de la jerarquia

protected ocupa una posició intermèdia entre private (només la pròpia classe) i public (tot el programa): és visible per a les classes hereves, però continua ocult per a la resta de codi extern a la jerarquia. Aquest curs farà servir sobretot public i private; protected apareixerà de forma puntual quan una classe base necessiti compartir alguna cosa amb les seves hereves sense exposar-ho a la resta del programa, i internal no farà falta als exemples de BiblioTech, ja que tot el projecte es compila com una única aplicació.

  1. El problema concret: Disponible modificable lliurement

Així estava definida, fins ara, la propietat Disponible:

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

    // ...
}

Amb { get; set; } públics, res impedeix aquest codi, en qualsevol part del programa:

Llibre llibre1 = new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4");
llibre1.Disponible = true; // Es salta per complet la logica de Prestar()/Retornar()

Aquest Disponible = true directe no passa per cap comprovació: no verifica si hi havia un préstec en curs, no registra cap missatge, no aplica cap regla de negoci. El resultat és un objecte l'estat del qual (Disponible) pot quedar desincronitzat de la resta del sistema (per exemple, dels objectes Prestec que referencien aquell llibre).

  1. private set: exposar lectura, restringir escriptura

La solució més directa és permetre que Disponible es llegeixi des de qualsevol part (public get), però que només s'escrigui des de dins de la pròpia classe (private set):

class MaterialBibliotecari
{
    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; // Ok: estem dins de la propia classe
            Console.WriteLine($"'{Titol}' ha estat prestat.");
        }
        else
        {
            Console.WriteLine($"'{Titol}' no esta disponible per a prestec.");
        }
    }

    public void Retornar()
    {
        Disponible = true; // Ok: estem dins de la propia classe
        Console.WriteLine($"'{Titol}' ha estat retornat.");
    }
}
Llibre llibre1 = new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4");

Console.WriteLine(llibre1.Disponible); // Ok: lectura publica, True
llibre1.Prestar();                     // Ok: canvia Disponible internament, a traves del metode
// llibre1.Disponible = true;          // Error de compilacio: el set es private, no accessible des de fora

Amb private set, l'última línia ja no compila: el compilador impedeix, ara de forma garantida (no només per convenció o bona voluntat), que codi extern a la classe assigni un valor a Disponible sense passar per Prestar() o Retornar(). Aquesta és la forma més simple i habitual d'encapsular una propietat en C#: exposar la lectura, tancar l'escriptura dins d'operacions amb significat (Prestar, Retornar), en lloc d'un set genèric.

  1. Propietats amb lògica de validació al set

private set resol el cas de Disponible, però de vegades cal alguna cosa més flexible: permetre l'escriptura des de fora, però validant el valor abans d'acceptar-lo. Per a això cal una propietat completa, amb un camp (backing field) explícit al darrere:

class MaterialBibliotecari
{
    private string _titol;

    public string Titol
    {
        get { return _titol; }
        set
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                throw new ArgumentException("El titol no pot estar buit.");
            }

            _titol = value;
        }
    }

    // Autor, Disponible, constructor, Prestar(), Retornar() com a l'apartat anterior
}

Dins del bloc set, la paraula clau value representa automàticament el valor que s'està intentant assignar. Aquí, si algú intenta posar un títol buit o compost només d'espais (string.IsNullOrWhiteSpace), la propietat llança una ArgumentException —recorda el maneig d'excepcions del Mòdul 2— en lloc d'acceptar una dada no vàlida. _titol, amb guió baix inicial en minúscula, és la convenció habitual en C# per anomenar el camp privat que dona suport a una propietat amb lògica pròpia.

Llibre llibre1 = new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4");
llibre1.Titol = "";
// Llanca ArgumentException: "El titol no pot estar buit."
Propietat autoimplementada ({ get; set; }) Propietat completa amb validació
Sintaxi Una sola línia, sense camp visible get/set explícits, amb camp _titol propi
Permet validar el valor abans d'acceptar-lo No
Quan usar-la La dada no necessita cap restricció especial Hi ha una regla de negoci que s'ha de complir sempre

  1. Propietats de només lectura

Un tercer nivell de restricció, més estricte que private set, és no declarar cap set en absolut: la propietat només es pot assignar dins del constructor (o mitjançant un inicialitzador), i mai més després:

class Soci
{
    public int Id { get; }        // nomes lectura: mai canvia despres de crear l'objecte
    public string Nom { get; set; }

    public Soci(int id, string nom)
    {
        Id = id;
        Nom = nom;
    }
}

public int Id { get; } (sense set de cap tipus) expressa la intenció amb total claredat: l'identificador d'un soci es fixa en crear-lo i no ha de canviar mai durant tota la vida de l'objecte. Intentar escriure soci1.Id = 5; fora del constructor seria un error de compilació, igual que passaria amb private set, però aquí queda clar que ni tan sols la pròpia classe necessita reassignar-lo més endavant.

  1. Aplicant encapsulació a la resta del model: Soci i Prestec

Amb aquestes tres tècniques (private set, propietats amb validació, i propietats de només lectura), es pot revisar la resta del model de BiblioTech perquè cada dada s'exposi únicament amb el nivell de control que li correspon:

class Prestec
{
    public Llibre Llibre { get; }
    public Soci Soci { get; }
    public DateTime DataPrestec { get; }
    public DateTime? DataDevolucio { get; private set; }

    public Prestec(Llibre llibre, Soci soci)
    {
        Llibre = llibre;
        Soci = soci;
        DataPrestec = DateTime.Now;
        DataDevolucio = null;
    }

    public void RegistrarDevolucio()
    {
        DataDevolucio = DateTime.Now;
    }
}

Ara Llibre, Soci i DataPrestec d'un Prestec són de només lectura (no té sentit que un préstec "canviï" de llibre o de soci una vegada creat), i DataDevolucio només es pot fixar des de dins de la pròpia classe Prestec, a través del mètode RegistrarDevolucio() —mai assignant la data directament des de fora:

Llibre llibre1 = new Llibre("Ficcions", "Jorge Luis Borges", "978-84-376-0496-1");
Soci soci1 = new Soci(1, "Ana Martinez");

llibre1.Prestar();
Prestec prestec1 = new Prestec(llibre1, soci1);

// prestec1.DataDevolucio = DateTime.Now; // Error de compilacio: el set es private
prestec1.RegistrarDevolucio();             // forma correcta i unica de registrar la devolucio
llibre1.Retornar();

  1. El principi de mínima exposició

Tot el que s'ha vist en aquesta lliçó es resumeix en un únic principi pràctic: exposa de la teva classe únicament el mínim que la resta del programa necessita fer servir, i res més. Cada propietat, mètode o camp públic és una promesa cap a la resta del codi: com més n'exposis, més formes tindrà la resta del programa d'acoblar-se als detalls interns de la teva classe, i més difícil serà canviar aquests detalls demà sense trencar alguna cosa. Abans de marcar alguna cosa com a public, pregunta't si de veritat ho ha de ser, o si n'hi hauria prou amb private (o, en el cas de propietats, amb restringir només el set).

Regla pràctica Aplicada a BiblioTech
Si una dada no ha de canviar després de crear-se, fes-la de només lectura Soci.Id, Prestec.Llibre, Prestec.Soci, Prestec.DataPrestec
Si una dada només ha de canviar mitjançant una operació concreta, restringeix el set MaterialBibliotecari.Disponible (només via Prestar()/Retornar()), Prestec.DataDevolucio (només via RegistrarDevolucio())
Si una dada necessita complir una regla en assignar-se, fes servir una propietat completa amb validació MaterialBibliotecari.Titol
Si una dada pot canviar lliurement sense cap restricció, un { get; set; } públic és suficient Soci.Nom

Errors Comuns i Consells

  • Deixar totes les propietats com a { get; set; } públic "per si de cas": és l'opció més còmoda a curt termini, però renuncia a tota la protecció que ofereix l'encapsulació; revisa, propietat a propietat, si de veritat necessita poder escriure's lliurement des de fora.
  • Oblidar inicialitzar un camp private amb propietat completa: si defineixes _titol sense cap valor per defecte ni assignació al constructor, la propietat Titol retornaria null fins a la primera assignació vàlida; assegura't que el constructor passi sempre pel set (assignant Titol = titol;, no _titol = titol; directament, perquè la validació també s'apliqui a la construcció).
  • Confondre private set amb private a seques: private set continua permetent la lectura pública (get continua sent public); el que es restringeix és només l'escriptura. Un camp completament private no seria accessible ni per llegir des de fora de la classe.
  • Fer servir protected com a solució per defecte: protected exposa el membre a totes les classes hereves presents i futures, la qual cosa també és una forma d'acoblament; reserva'l per a quan de veritat una classe base necessiti compartir alguna cosa amb les seves hereves.
  • Consell: en dissenyar una classe nova, comença marcant-ho tot com a private i vés obrint únicament allò que la resta del programa necessiti de veritat, en lloc de començar amb tot public i restringir després; és molt més fàcil obrir accés més endavant que tancar-lo sense trencar codi que ja en depenia.

Exercicis

  1. Modifica la propietat Disponible de MaterialBibliotecari perquè tingui un get públic i un set privat (private set). Comprova que, després de crear un objecte Llibre, la línia llibre1.Disponible = true; escrita fora de la classe provoca un error de compilació, mentre que llibre1.Prestar() continua funcionant amb normalitat.

  2. Converteix la propietat Titol de MaterialBibliotecari en una propietat completa amb un camp privat _titol, el set del qual llanci una ArgumentException amb el missatge "El titol no pot estar buit." si el valor rebut és una cadena buida o només espais (fes servir string.IsNullOrWhiteSpace). Prova a assignar un títol buit i comprova que es llança l'excepció.

  3. A la classe Prestec, declara DataDevolucio com a DateTime? amb get públic i set privat, i afegeix un mètode void RegistrarDevolucio() que li assigni DateTime.Now. Crea un Prestec, comprova que DataDevolucio comença en null, i després de cridar RegistrarDevolucio(), comprova que ja té un valor.

Solucions

class MaterialBibliotecari
{
    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;
        }
    }
}

// llibre1.Disponible = true; // Error de compilacio: el set es private
private string _titol;

public string Titol
{
    get { return _titol; }
    set
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            throw new ArgumentException("El titol no pot estar buit.");
        }

        _titol = value;
    }
}

En executar llibre1.Titol = "";, es llança ArgumentException amb el missatge "El titol no pot estar buit.", ja que la cadena buida compleix la condició de string.IsNullOrWhiteSpace.

class Prestec
{
    public Llibre Llibre { get; }
    public Soci Soci { get; }
    public DateTime DataPrestec { get; }
    public DateTime? DataDevolucio { get; private set; }

    public Prestec(Llibre llibre, Soci soci)
    {
        Llibre = llibre;
        Soci = soci;
        DataPrestec = DateTime.Now;
        DataDevolucio = null;
    }

    public void RegistrarDevolucio()
    {
        DataDevolucio = DateTime.Now;
    }
}

Prestec prestec1 = new Prestec(new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4"), new Soci(1, "Ana Martinez"));
Console.WriteLine(prestec1.DataDevolucio); // (buit / null)
prestec1.RegistrarDevolucio();
Console.WriteLine(prestec1.DataDevolucio); // data i hora actuals

Conclusió

En aquesta lliçó has protegit l'estat intern del model de BiblioTech: els modificadors d'accés (public, private, protected, internal), la restricció d'escriptura amb private set, les propietats completes amb validació al set, i les propietats de només lectura, aplicades a MaterialBibliotecari.Disponible, MaterialBibliotecari.Titol i Prestec.DataDevolucio. Ja no és possible deixar un llibre o un préstec en un estat incoherent saltant-se la lògica de negoci: tota modificació passa, obligatòriament, pels mètodes pensats per a això.

Queda encara un cap solt conceptual: MaterialBibliotecari es pot instanciar directament amb new MaterialBibliotecari("...", "..."), encara que a la pràctica no representa cap material real de BiblioTech (només té sentit com a base de Llibre o Revista). A la propera lliçó, Abstracció, aprendràs a impedir aquesta instanciació directa convertint MaterialBibliotecari en una classe abstract, deixant clar en el propi codi que el seu únic propòsit és servir de base a altres classes.

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