5.5 Encapsulación, control de acceso y visibilidad
Dentro de la Programación Orientada a Objetos ya has visto que es muy importante el concepto de ocultación, la cual ha sido lograda gracias a la encapsulación de la información dentro de las clases. De esta manera una clase puede ocultar parte de su contenido o restringir el acceso a él para evitar que sea manipulado de manera inadecuada. Los modificadores de acceso en Java permiten especificar el ámbito de visibilidad de los miembros de una clase, proporcionando así un mecanismo de accesibilidad a varios niveles.
Acabas de estudiar que cuando se definen los miembros de una clase (atributos o métodos), e incluso la propia clase, se indica (aunque sea por omisión) un modificador de acceso. En función de la visibilidad que se desee que tengan los objetos o los miembros de esos objetos se elegirá alguno de los modificadores de acceso que has estudiado. Ahora que ya sabes cómo escribir una clase completa (declaración de la clase, declaración de sus atributos y declaración de sus métodos), vamos a hacer un repaso general de las opciones de visibilidad (control de acceso) que has estudiado.
Los modificadores de acceso determinan si una clase puede utilizar determinados miembros (acceder a atributos o invocar miembros) de otra clase. Existen dos niveles de control de acceso:
- A nivel general (nivel de clase): visibilidad de la propia clase.
- A nivel de miembros: especificación, miembro por miembro, de su nivel de visibilidad.
En el caso de la clase, ya estudiaste que los niveles de visibilidad podían ser:
- Público (modificador
public), en cuyo caso la clase era visible a cualquier otra clase (cualquier otro fragmento de código del programa). - Privada al paquete (
package)(sin modificador o modificador "por omisión"). En este caso, la clase sólo será visible a las demás clases del mismo paquete, pero no al resto del código del programa (otros paquetes). - (protected), lo podrán ver las clases del mismo paquete y también las clases herederas.
En el caso de los miembros, disponías de una posibilidad más de niveles de accesibilidad, teniendo un total de cuatro opciones a la hora de definir el control de acceso al miembro:
- Público (modificador
public), igual que en el caso global de la clase y con el mismo significado (miembro visible desde cualquier parte del código). - Del paquete (sin modificador), también con el mismo significado que en el caso de la clase (miembro visible sólo desde clases del mismo paquete, ni siquiera será visible desde una subclase salvo si ésta está en el mismo paquete).
- Privado (modificador
private), donde sólo la propia clase tiene acceso al miembro. - Protegido (modificador
protected), lo podrán ver las clases del mismo paquete y también las clases herederas.
1. Ocultación de atributos. Métodos de acceso¶
Los atributos de una clase suelen ser declarados como privados a la clase o, como mucho, protected (accesibles también por clases heredadas), pero no como public. De esta manera puedes evitar que sean manipulados inadecuadamente (por ejemplo modificarlos sin ningún tipo de control) desde el exterior del objeto.
En estos casos lo que se suele hacer es declarar esos atributos como privados o protegidos y crear métodos públicos que permitan acceder a esos atributos. Si se trata de un atributo cuyo contenido puede ser observado pero no modificado directamente, puede implementarse un método de "obtención" del atributo (en inglés se les suele llamar método de tipo get) y si el atributo puede ser modificado, puedes también implementar otro método para la modificación o "establecimiento" del valor del atributo (en inglés se le suele llamar método de tipo set). Esto ya lo has visto en apartados anteriores.
Si recuerdas la clase Punto que hemos utilizado como ejemplo, ya hiciste algo así con los métodos de obtención y establecimiento de las coordenadas:
Así, para poder obtener el valor del atributo x de un objeto de tipo Punto será necesario utilizar el método obtenerX() y no se podrá acceder directamente al atributo x del objeto.
En algunos casos los programadores directamente utilizan nombres en inglés para nombrar a estos métodos:
También pueden darse casos en los que no interesa que pueda observarse directamente el valor de un atributo, sino un determinado procesamiento o cálculo que se haga con el atributo (pero no el valor original). Por ejemplo podrías tener un atributo DNI que almacene los 8 dígitos del DNI pero no la letra del NIF (pues se puede calcular a partir de los dígitos). El método de acceso para el DNI (método getDNI) podría proporcionar el DNI completo (es decir, el NIF, incluyendo la letra), mientras que la letra no es almacenada realmente en el atributo del objeto. Algo similar podría suceder con el dígito de control de una cuenta bancaria, que puede no ser almacenado en el objeto, pero sí calculado y devuelto cuando se nos pide el número de cuenta completo.
En otros casos puede interesar disponer de métodos de modificación de un atributo pero a través de un determinado procesamiento previo para, por ejemplo, poder controlar errores o valores inadecuados. Volviendo al ejemplo del NIF, un método para modificar un DNI (método setDNI) podría incluir la letra (NIF completo), de manera que así podría comprobarse si el número de DNI y la letra coinciden (es un NIF válido). En tal caso se almacenará el DNI y en caso contrario se producirá un error de validación (por ejemplo lanzando una excepción). En cualquier caso, el DNI que se almacenara sería solamente el número y no la letra (pues la letra es calculable a partir del número de DNI).
2. Ocultación de métodos¶
Normalmente los métodos de una clase pertenecen a su interfaz y por tanto parece lógico que sean declarados como públicos. Pero también es cierto que pueden darse casos en los que exista la necesidad de disponer de algunos métodos privados a la clase. Se trata de métodos que realizan operaciones intermedias o auxiliares y que son utilizados por los métodos que sí forman parte de la interfaz. Ese tipo de métodos (de comprobación, de adaptación de formatos, de cálculos intermedios, etc.) suelen declararse como privados pues no son de interés (o no es apropiado que sean visibles) fuera del contexto del interior del objeto.
En el ejemplo anterior de objetos que contienen un DNI, será necesario calcular la letra correspondiente a un determinado número de DNI o comprobar si una determinada combinación de número y letra forman un DNI válido. Este tipo de cálculos y comprobaciones podrían ser implementados en métodos privados de la clase (o al menos como métodos protegidos).