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.
Modificador | Clase | Mismo paquete | Subclase | Otro 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
.
1class 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
.
1package biblioteca;2
3import biblioteca.Libro;4
5class 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
.
1class 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
.
1package usuarios;2
3import biblioteca.Libro;4
5class 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.
1class 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()
.
1class 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()
.
1class 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()
.
1import biblioteca.Libro;2
3class Main {4 public static void main(String[] args) {5 Libro libro = new Libro("Elantris");6 System.out.println(libro.getTitulo()); // Elantris7 libro.setTitulo("El imperio final");8 System.out.println(libro.getTitulo()); // El imperio final9 }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.
1class 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.
1class 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.