Saltar al contenido principal

Encapsulación, visibilidad y this

Una clase pierde el control de sus reglas si cualquier parte del programa puede escribir directamente sus atributos:

cuenta.saldo = -1000;

Si una cuenta no admite saldo negativo, ese estado nunca debería llegar a existir. Encapsular consiste en proteger el estado y ofrecer operaciones públicas que mantengan sus invariantes.

Estado privado y API pública​

class Cuenta {
private String titular;
private double saldo;

public Cuenta(String titular, double saldoInicial) {
this.titular = titular;
this.saldo = saldoInicial;
}
}
  • private limita el acceso a la propia clase.
  • public permite usar el constructor u operación desde otras clases.
  • sin modificador, el miembro es package-private: resulta accesible desde clases del mismo package. Lo retomaremos al organizar packages.

La visibilidad no se elige por costumbre: expresa quién debe poder usar o cambiar cada pieza.

this: el objeto actual​

Cuando un parámetro y un atributo comparten nombre, this distingue el atributo del objeto receptor:

public Cuenta(String titular) {
this.titular = titular;
}

Sin this, la asignación titular = titular asignaría el parámetro a sí mismo y no inicializaría el atributo.

Consultar sin exponer​

Un getter permite consultar un dato privado cuando el exterior realmente lo necesita:

public double getSaldo() {
return saldo;
}

Eso no obliga a crear un setter. Este diseño sería peligroso:

public void setSaldo(double saldo) {
this.saldo = saldo;
}

Permitiría setSaldo(-1000) y no expresa la operación del dominio. Para una cuenta son más coherentes ingresar y retirar.

Comportamiento que protege reglas​

public boolean retirar(double cantidad) {
if (cantidad <= 0 || cantidad > saldo) {
return false;
}

saldo -= cantidad;
return true;
}

La regla queda en un único lugar. El estado válido que siempre queremos conservar se denomina invariante.

Práctica inmediata​

Para cambiar el nombre de un alumno, ¿crearías siempre setNombre? Depende. Si el nombre debe estar limpio y no vacío, una operación cambiarNombre puede validar y comunicar mejor la intención. Si nunca debe cambiar, no ofrezcas ninguna operación.

final no significa objeto inmutable​

Un atributo final solo puede asignarse una vez:

class Pedido {
private final String codigo;

Pedido(String codigo) {
this.codigo = codigo;
}
}

Con referencias, final impide apuntar a otro objeto, pero no congela el objeto referenciado:

final Cuenta cuenta = new Cuenta("Ana", 100);
cuenta.ingresar(20); // válido: cambia el objeto
// cuenta = new Cuenta(...); // no compila: reasigna la referencia

La inmutabilidad requiere un diseño más amplio: estado privado y ausencia de operaciones que lo modifiquen, entre otras decisiones.

Programa completo​

Guarda como CuentaSeguraDemo.java:

class Cuenta {
private final String titular;
private double saldo;

public Cuenta(String titular, double saldoInicial) {
this.titular = titular;
this.saldo = saldoInicial >= 0 ? saldoInicial : 0;
}

public String getTitular() {
return titular;
}

public double getSaldo() {
return saldo;
}

public boolean ingresar(double cantidad) {
if (cantidad <= 0) {
return false;
}
saldo += cantidad;
return true;
}

public boolean retirar(double cantidad) {
if (cantidad <= 0 || cantidad > saldo) {
return false;
}
saldo -= cantidad;
return true;
}
}

public class CuentaSeguraDemo {
public static void main(String[] args) {
Cuenta cuenta = new Cuenta("Ana", 100);

System.out.println("Retirada 30: " + cuenta.retirar(30));
System.out.println("Retirada 100: " + cuenta.retirar(100));
System.out.println("Ingreso -5: " + cuenta.ingresar(-5));
System.out.println(cuenta.getTitular() + ": " + cuenta.getSaldo());
}
}

El segundo retiro y el ingreso negativo se rechazan, de modo que el saldo nunca queda en un estado inválido.

Decisiones alternativas​

  • Getter sin setter: lectura pública, cambio controlado.
  • Operación de comportamiento: expresa intención y protege reglas.
  • Constructor validado: evita empezar con un estado imposible.
  • package-private: útil para colaboración interna de un package, no para evitar pensar la API.

Errores habituales​

  • Crear getter y setter para cada atributo de forma automática.
  • Validar solo en main, dejando que otra clase pueda romper la regla.
  • Confundir this.saldo con una variable global.
  • Creer que final vuelve inmutable cualquier objeto.
  • Hacer todo public para resolver errores de acceso.

Mini reto: producto con stock válido​

Diseña Producto con nombre no reasignable y stock privado. El stock no puede ser negativo. Ofrece reponer, vender y getters necesarios; no crees setStock.

Solución razonada​

class Producto {
private final String nombre;
private int stock;

public Producto(String nombre, int stockInicial) {
this.nombre = nombre;
this.stock = stockInicial >= 0 ? stockInicial : 0;
}

public String getNombre() {
return nombre;
}

public int getStock() {
return stock;
}

public boolean reponer(int cantidad) {
if (cantidad <= 0) return false;
stock += cantidad;
return true;
}

public boolean vender(int cantidad) {
if (cantidad <= 0 || cantidad > stock) return false;
stock -= cantidad;
return true;
}
}

public class ProductoDemo {
public static void main(String[] args) {
Producto teclado = new Producto("Teclado", 3);
System.out.println(teclado.vender(2));
System.out.println(teclado.vender(2));
System.out.println(teclado.getNombre() + ": " + teclado.getStock());
}
}

La API permite operaciones reales y conserva stock >= 0. En JV2-04 haremos que varios objetos encapsulados colaboren sin concentrar todas las responsabilidades en una clase.