Ir al contenido
wannacode

Encapsulamiento

En resumen: El encapsulamiento significa que una clase oculta sus datos con private y los muestra solo a través de propiedades y métodos que comprueban los valores.

Problema

Menos 500 €

En la lección anterior pusimos la comprobación del retiro en un método. Pero el campo está abierto, y cualquiera puede escribir en él directamente:

Account account = new Account("María", 100);
account.Withdraw(500);
account.Balance = -500;
Console.WriteLine(account.Balance);
Salida
-500

El método se negó con razón a retirar 500 €. La línea siguiente simplemente fijó el saldo a mano. La comprobación existe, pero se puede saltar. Necesitamos una forma de decir «solo la cuenta toca el saldo».

La idea

private: solo la clase lo toca

Pon private delante del campo en lugar de public:

class Account
{
private string owner;
private double balance;
// ...
}

Ahora solo el código de dentro de la clase Account puede llegar al campo. Desde fuera, la línea

account.balance = -500;

ya no compila:

Error de compilación
error CS0122: 'Account.balance' is inaccessible due to its protection level

private y public se llaman modificadores de acceso. Dicen quién puede ver un campo, un método o un constructor. La clase oculta sus datos y los muestra solo a través de sus propios métodos. Esto se llama encapsulamiento.

En C#, los campos ocultos se escriben con minúscula: balance, owner. Por eso el constructor dice this.balance = balance;: ahora el campo y el parámetro se llaman igual.

Más precisamente, para curiosos
  • Sin modificador, un campo o un método en C# es private. Por eso hasta esta lección escribíamos public en todas partes.
  • Una clase sin modificador es internal: visible en todo el proyecto (el ensamblado), pero no desde otros proyectos. Por eso class Product funciona en todo nuestro programa.
  • También existe protected: visible en la clase y en sus clases hijas. Lo conocerás en el módulo «Herencia y polimorfismo».

La idea

Leer sin tocar

Desde fuera, ahora no podemos ni siquiera leer el saldo. Así que la clase ofrece un método que solo lo devuelve:

Program.cs
class Account
{
private string owner;
private double balance;
public Account(string owner, double balance)
{
this.owner = owner;
this.balance = balance;
}
public string Owner
{
get { return owner; }
}
public double Balance
{
get { return balance; }
}
public void Deposit(double amount)
{
if (amount > 0)
{
balance += amount;
}
}
public bool Withdraw(double amount)
{
if (amount > 0 && amount <= balance)
{
balance -= amount;
return true;
}
return false;
}
}
class Program
{
static void Main()
{
Account account = new Account("María", 100);
account.Withdraw(30);
account.Deposit(50);
Console.WriteLine(account.Owner + ": " + account.Balance + " €");
}
}
Salida
María: 120 €

Balance es una propiedad. Desde fuera la usas como un campo: account.Balance. Por dentro es un método: get devuelve el valor del campo oculto balance. Este es el getter de C#. El saldo cambia solo a través de Deposit y Withdraw, y ellos hacen las comprobaciones.

Ahora Withdraw devuelve bool: si el retiro salió bien. Así quien retira sabe si funcionó.

Account account = new Account("María", 100);
account.Withdraw(500);
account.Deposit(50);
Console.WriteLine(account.Balance);
Paso a paso, en palabras
  1. balance es private: el cristal significa que desde fuera no hay camino hacia el campo.
  2. El retiro pasa por la comprobación del método: 500 es más que 100. Rechazado, y el saldo se queda como está.
  3. El depósito también se comprueba: 50 es un importe positivo. El método lo suma.
  4. La propiedad Balance entrega una copia del saldo. El campo en sí sigue oculto.

Pruébalo

Rechazado

¿Qué imprimirá el programa?

Account account = new Account("María", 100);
Console.WriteLine(account.Withdraw(150));
account.Deposit(-20);
Console.WriteLine(account.Balance);
Respuesta

La idea

Setter: un cambio con comprobación

A veces el código de fuera necesita cambiar un campo, pero no a cualquier valor. La edad de un animal puede cambiar, pero no puede ser negativa:

class Animal
{
private int age;
public int Age
{
get { return age; }
set
{
if (value >= 0)
{
age = value;
}
}
}
}
Animal cat = new Animal();
cat.Age = 4;
cat.Age = -3;
Console.WriteLine(cat.Age);
Salida
4

Ahora la propiedad también tiene un set: es el setter. Con cat.Age = -3, se ejecuta el código de set, y value es el valor a la derecha de =, aquí -3. La comprobación falla y la edad se queda en 4.

La idea

¿Qué campo lleva setter?

No todos los campos necesitan un setter, y algunos ni siquiera un getter. Piensa en lo que el código de fuera debería poder hacer con el producto:

  • el nombre y el precio se fijan al crear el producto y después solo se leen: solo getter;
  • la cantidad en el carrito cambia, pero es como mínimo 1: un getter y un setter con comprobación.
class Product
{
private string name;
private double price;
private int quantity;
public Product(string name, double price)
{
this.name = name;
this.price = price;
this.quantity = 1;
}
public string Name
{
get { return name; }
}
public double Price
{
get { return price; }
}
public int Quantity
{
get { return quantity; }
set
{
if (value >= 1)
{
quantity = value;
}
}
}
}
Product coffee = new Product("Café", 7.50);
coffee.Quantity = 3;
coffee.Quantity = 0;
Console.WriteLine(coffee.Name + " x" + coffee.Quantity);
Salida
Café x3

La regla es: empieza con private para todo y abre solo lo que necesitas. Un setter que guarda cualquier cosa sin comprobar trae de vuelta el problema de la primera tarjeta.

La idea

Propiedades en corto

Una propiedad que solo lee y escribe un campo sin comprobación se puede escribir en una línea. C# crea él mismo el campo oculto:

class Product
{
public string Name { get; set; }
public double Price { get; set; }
public int Quantity { get; set; } = 1;
}
Product coffee = new Product { Name = "Café", Price = 7.50 };
Console.WriteLine(coffee.Name + " x" + coffee.Quantity + ": " + coffee.Price + " €");
Salida
Café x1: 7.5 €

{ get; set; } es una propiedad autoimplementada. Con = 1 tiene un valor por defecto. new Product { Name = "Café", Price = 7.50 } es un inicializador de objeto: crea el objeto y fija sus propiedades en un solo par de llaves.

Puedes ocultar solo la escritura. { get; private set; } se puede leer desde cualquier parte, pero cambiar solo en la clase. { get; } se fija solo en el constructor:

class Account
{
public string Owner { get; }
public double Balance { get; private set; }
public Account(string owner, double balance)
{
Owner = owner;
Balance = balance;
}
public void Deposit(double amount)
{
if (amount > 0)
{
Balance += amount;
}
}
}
Account account = new Account("María", 100);
account.Deposit(20);
Console.WriteLine(account.Owner + ": " + account.Balance + " €");
Salida
María: 120 €

La línea account.Balance = -500; en Main no compila:

Error de compilación
error CS0272: The property or indexer 'Account.Balance' cannot be used in this context because the set accessor is inaccessible

Pruébalo

¿Qué falta?

El saldo solo debe verse dentro de la clase. ¿Qué va en el hueco?
class Account
{
     double balance;
}

Pruébalo

Encuentra el error

El campo es private, y aun así el saldo pasa a ser -500:

Account account = new Account();
account.Balance = 100;
account.Withdraw(30);
account.Balance = -500;
Console.WriteLine(account.Balance);
Salida
-500

¿Qué línea de la clase rompe la protección?

Elige la línea con el error.

Ejercicio

Datos ocultos

Resuelve las tareas en tu editor. Todos los campos son private.

1. Persona. Person con un nombre y una edad, un constructor, propiedades de solo lectura Name y Age y Introduce().

Salida
María tiene 34 años
Hola, soy María y tengo 34 años

2. Una cuenta sin números rojos. Account con un titular y un saldo. Un depósito acepta solo un importe positivo. Un retiro devuelve si salió bien y nunca deja que el saldo baje de 0. El código de fuera puede leer el saldo, pero no escribirlo.

Main
Account account = new Account("Pablo", 50);
account.Deposit(25);
account.Deposit(-10);
Console.WriteLine(account.Withdraw(100));
Console.WriteLine(account.Withdraw(40));
Console.WriteLine("Saldo: " + account.Balance + " €");
Salida
False
True
Saldo: 35 €

3. Animal. Animal con una especie y una edad. La especie se fija en el constructor y después solo se lee. La edad tiene un set que rechaza un número negativo e imprime un mensaje. El constructor también pasa por la misma comprobación.

Main
Animal cat = new Animal("gato", 3);
cat.Age = 4;
cat.Age = -1;
Console.WriteLine(cat.Species + ", " + cat.Age + " años");
Salida
Edad no válida: -1
gato, 4 años

Más tareas

  • Nota. Una clase Grade con el nombre de una asignatura y una nota de 2 a 6. El setter rechaza todo lo que está fuera de esos límites. Pruébalo con 5, 7 y 1.
  • Producto con descuento. Añade al Product de la lección un método que aplica un descuento en porcentaje y acepta solo del 1 al 90 %. El precio no tiene setter.

Test

Ponte a prueba

1. El campo balance es private. ¿Desde dónde se puede cambiar?
2. ¿Por qué la cuenta no tiene un setter para el saldo?
3. En el set de una propiedad, ¿qué es value?
4. ¿Qué significa encapsulamiento?

Creado por Ivo Gergov como parte de la campaña de Evo Tech por una educación accesible en programación ·