Saltearse al contenido

Encapsulamiento

Como vimos previamente, todos los atributos y/o métodos definidos en una clase pueden ser accedidos desde cualquier instancia de esta clase, incluso podemos acceder a algunos métodos directamente desde la clase. Dicho de otro modo, podemos manipular directamente las características de un objeto o emplear un comportamiento siempre que lo deseemos.

1
<objeto>.<atributo>;
2
3
<objeto>.<metodo>();

En particular podríamos cuestionarnos si es correcto que se pueda modificar cualquier atributo de un objeto en cualquier momento. En ocasiones, estos atributos pueden almacenar datos sensibles.

Por otro lado, y más importante aún, es que alterar el estado de un objeto arbitrariamente puede generar errores en nuestro sistema. Por lo cual, puede ser necesario que un atributo solo pueda ser modificado en ciertas circunstancias.

Por este motivo, es necesario que podamos controlar el entorno en el cual se podrá acceder a los atributos, y en ciertas ocasiones los métodos, de una clase. Como solución a esta problemática surge el pilar del encapsulamiento.

De esta manera se protege la integridad interna de los datos de una clase.

Muchos lenguajes de programación implementan una sintaxis especial para brindar soporte a este concepto. En Java, para lograr el encapsulamiento de los objetos, se emplean los modificadores de acceso.

Modificadores de acceso

En Java, existen 4 modificadores de acceso que nos permiten definir el nivel de acceso que tendrá un atributo o método de una clase, incluso podemos restringir el acceso a la clase misma. Estos modificadores son:

  • public
  • protected
  • private
  • default

Para comenzar, tomaremos en cuenta sólo la aplicación de modificadores de acceso a los atributos y métodos de una clase.

Es importante conocer la diferencia entre cada uno de estos modificadores, ya que dependiendo del modificador que utilicemos la visibilidad de los atributos y métodos de una clase cambiará.

Para tener una mejor comprensión de los modificadores de acceso, estableceremos la siguiente tabla de visibilidad y una estructura de proyecto para ejemplificar cada caso.

ModificadorClaseMismo paqueteSubclaseOtro paquete
public
protected
default
private
  • Directorymi-proyecto/
    • Directorysrc/
      • Directorybiblioteca/ Paquete para clases de la biblioteca
        • Libro.java
        • Prestamo.java
      • Directoryusuarios/ Paquete para gestionar usuarios
        • Administrador.java
        • Usuario.java
      • App.java Clase principal

Modificador default

Hasta el momento, al definir atributos y métodos, no hemos especificado ningún modificador de acceso, por lo que se ha establecido el modificador por defecto, el cual es default.

Supongamos que en la siguiente clase Libro, no hemos especificado ningún modificador de acceso para el atributo titulo, por lo que se ha establecido el modificador default.

1
class Libro {
2
String titulo;
3
}

Entonces, ateniéndonos a la tabla de visibilidad, el atributo titulo de la clase Libro podrá ser accedido desde cualquier clase que se encuentre en el mismo paquete, es decir, desde la clase Prestamo podremos acceder al atributo titulo de un objeto de la clase Libro.

1
package biblioteca;
2
3
import biblioteca.Libro;
4
5
class Prestamo {
6
void mostrarTitulo(Libro libro) {
7
System.out.println(libro.titulo);
8
}
9
}

Sin embargo, si intentamos acceder al atributo titulo desde una clase de otro paquete, por ejemplo, desde la clase Administrador, obtendremos un error de compilación.

Modificador public

Para establecer cualquier otro modificador de acceso que no sea el modificador default, debemos especificarlo explícitamente al momento de definir el atributo o método. Para esto, debemos anteponer el modificador de acceso al atributo o método, en este caso, el modificador public.

1
class Libro {
2
public String titulo;
3
}

Ahora el atributo titulo de la clase Libro podrá ser accedido desde cualquier clase, sin importar si se encuentra en el mismo paquete o no. Por ejemplo, desde la clase Administrador podremos acceder al atributo titulo de un objeto de la clase Libro.

1
package usuarios;
2
3
import biblioteca.Libro;
4
5
class Administrador {
6
void cambiarTitulo(Libro libro, String nuevoTitulo) {
7
libro.titulo = nuevoTitulo;
8
}
9
}

Modificador private

Por otro lado, si establecemos el modificador private a un atributo o método, este solo podrá ser accedido desde la misma clase, es decir, no podremos acceder a este atributo desde ninguna otra ámbito. Este es el modificador de acceso más restrictivo.

1
class Libro {
2
private String titulo;
3
}

Veremos que si intentamos acceder al atributo titulo desde cualquier otra clase del proyecto que no sea la clase Libro, obtendremos un error de compilación.

De igual manera, podemos aplicar estos modificadores de acceso a los métodos de una clase.

Garantizar el encapsulamiento

Ahora que conocemos los modificadores de acceso, podemos establacer ciertas pautas para garantizar el encapsulamiento de los objetos.

  • Todos los atributos de una clase deben ser privados o, a lo sumo, protegidos (si es que aplicamos herencia).
  • Todos los métodos de una clase que permitan conocer o modificar el estado de un objeto deben ser públicos o, a lo sumo, protegidos.
  • Un método solo puede ser privado si realiza una operación auxiliar que no es necesaria que sea visible para el resto de las clases.

Getters y setters

Tomando en cuenta las pautas mencionadas anteriormente, surgen ciertos métodos que podemos considerar especiales, los cuales nos permiten acceder o modificar un atributo de una clase. Estos métodos son conocidos como getters y setters.

Un getter es un método que nos permite acceder a un atributo de una clase. Como su nombre lo indica, este método devuelve el valor del atributo.

Si bien no existe una regla sintáctica para definir un getter, se ha establecido por convención que el nombre de este método debe comenzar con la palabra get, seguido del nombre del atributo con la primera letra en mayúscula.

Por ejemplo, si queremos acceder al atributo titulo de la clase Libro, podemos definir un método getTitulo().

1
class Libro {
2
private String titulo;
3
4
Libro(String titulo) {
5
this.titulo = titulo;
6
}
7
8
public String getTitulo() {
9
return this.titulo;
10
}
11
}

Por otro lado, un setter es un método que nos permite modificar el valor de un atributo de una clase. Para esto debemos pasar como argumento el nuevo valor que queremos asignar al atributo.

Al igual que con los getters, no existe una regla sintáctica para definir un setter, pero se ha establecido por convención que el nombre de este método debe comenzar con la palabra set, seguido del nombre del atributo con la primera letra en mayúscula.

Por ejemplo, si queremos modificar el atributo titulo de la clase Libro, podemos definir un método setTitulo().

1
class Libro {
2
private String titulo;
3
4
Libro(String titulo) {
5
this.titulo = titulo;
6
}
7
8
public String getTitulo() {
9
return this.titulo;
10
}
11
12
public void setTitulo(String titulo) {
13
this.titulo = titulo;
14
}
15
}

De esta manera, podemos garantizar el encapsulamiento de los objetos. Si quisiéramos conocer o alterar el título de una instancia de la clase Libro, deberíamos hacerlo mediante los métodos getTitulo() y setTitulo().

1
import biblioteca.Libro;
2
3
class Main {
4
public static void main(String[] args) {
5
Libro libro = new Libro("Elantris");
6
System.out.println(libro.getTitulo()); // Elantris
7
libro.setTitulo("El imperio final");
8
System.out.println(libro.getTitulo()); // El imperio final
9
}
10
}

Por último, debemos considerar la posibilidad de que existan atributos de un objeto que queremos que sean inalterables una vez instanciados. Lógiamente, este atributo será privado y no tendrá un setter. Sin embargo, puede tener un getter de ser necesario.

Por ejemplo, si queremos que el atributo isbn de la clase Libro sea inalterable, dado representa un código único de identificación de un libro. No deberíamos permitir su modificación, dado que podría comprometer la integridad de nuestro sistema.

1
class Libro {
2
private String isbn;
3
4
Libro(String isbn) {
5
this.isbn = isbn;
6
}
7
8
public String getIsbn() {
9
return this.isbn;
10
}
11
}

En este caso, es de utilidad conocer el valor del atributo isbn, pero no deberíamos permitir su modificación. Por lo tanto, únicamente definimos un getter para este atributo.

Visibilidad de un constructor

Dado que un constructor es un método, este también puede tener un modificador de acceso.

En este caso, el modificador de acceso que apliquemos deberá ser siempre public, o default. De otra manera, no podremos instanciar objetos fuera del ámbito de la clase.

En el futuro, al declarar cualquier constructor, recomendamos siempre aplicar el modificador de acceso public. Esta es una buena práctica que podemos aplicar al programar en Java.

1
class Libro {
2
private String isbn;
3
4
public Libro(String isbn) {
5
this.isbn = isbn;
6
}
7
8
public String getIsbn() {
9
return this.isbn;
10
}
11
}

Modificador de acceso de una clase

Como mencionamos al comenzar este tema, también podemos aplicar modificadores de acceso a la clases que definimos.

En estos casos, los únicos modificadores que tiene sentido aplicar son public.

Debido a que si aplicamos el modificador private o protected, no podremos acceder a la clase desde ningún otro ámbito. Por lo cual, la clase sería complemente inútil.

Si por el contrario no indicamos ningún modificador de acceso, se aplicará el modificador default, pero esto también podría llegar a ser un problema.

Por norma general, se recomienda que las clases sean públicas.