Documentos de Académico
Documentos de Profesional
Documentos de Cultura
com
Las palabras resbalan. Como proclamó Humpty Dumpty en Lewis Carroll'sA través del espejo "Cuando uso
una palabra, significa exactamente lo que yo elijo que signifique, ni más ni menos ". Ciertamente, el uso
común de las palabrasencapsulamiento yescondite de información parece seguir esa lógica. Los autores
rara vez distinguen entre los dos y, a menudo, afirman directamente que son iguales.
¿Eso lo hace así? No para mí. Si fuera simplemente una cuestión de palabras, no escribiría una palabra
más sobre el tema. Pero hay dos conceptos distintos detrás de estos términos, conceptos engendrados
por separado y mejor entendidos por separado.
La encapsulación se refiere a la agrupación de datos con los métodos que operan sobre esos datos. A menudo, esa
definición se malinterpreta en el sentido de que los datos están ocultos de alguna manera. En Java, puede tener datos
encapsulados que no están ocultos en absoluto.
Sin embargo, ocultar datos no es todo el alcance de la ocultación de información. David Parnas introdujo por
primera vez el concepto de ocultamiento de información alrededor de 1972. Argumentó que el criterio principal
para la modularización del sistema debería referirse al ocultamiento de decisiones críticas de diseño. Hizo
hincapié en ocultar "decisiones de diseño difíciles o decisiones de diseño que probablemente cambiarán".
Ocultar información de esa manera aísla a los clientes de requerir un conocimiento profundo del diseño para
usar un módulo y de los efectos de cambiar esas decisiones.
En este artículo, exploro la distinción entre encapsulación y ocultación de información a través del
desarrollo de código de ejemplo. La discusión muestra cómo Java facilita la encapsulación e investiga las
ramificaciones negativas de la encapsulación sin ocultar datos. Los ejemplos también muestran cómo
mejorar el diseño de clases mediante el principio de ocultación de información.
Clase de posición
Con una conciencia cada vez mayor del enorme potencial de Internet inalámbrico, muchos expertos esperan que los
servicios basados en la ubicación brinden una oportunidad para la primera aplicación asesina inalámbrica. Para el
código de muestra de este artículo, elegí una clase que representa la ubicación geográfica de un punto en la superficie
de la tierra. Como entidad de dominio, la clase, denominadaPosición, representa información del sistema de posición
global (GPS). Un primer corte en la clase parece tan simple como:
System.out.println
("De mi casa en (" +
myHouse.latitude + "," + myHouse.longitude +
") a la cafetería en (" +
coffeeShop.latitude + "," + coffeeShop.longitude +
") es una distancia de" + distancia +
"en un título de" + título + "grados".
);
El código genera el resultado a continuación, que indica que la cafetería está al oeste (270,8
grados) de mi casa a una distancia de 6,09. La discusión posterior aborda la falta de unidades de
distancia.
================================================
=================
Desde mi casa en (36.538611, -121.7975) a la cafetería en
(36.539722, -121.907222) es la distancia de 6.0873776351893385 a
rumbo de 270,7547022304523 grados.
================================================
=================
Aunque el código puede usar objetos Java, lo hace de una manera que recuerda a una era pasada: funciones de utilidad
que operan en estructuras de datos. ¡Bienvenidos a 1972! Mientras el presidente Nixon se acurrucaba sobre grabaciones
secretas, los profesionales de la informática que codificaban en el lenguaje procedimental Fortran utilizaron con
entusiasmo la nueva Biblioteca Internacional de Matemáticas y Estadísticas (IMSL) precisamente de esta manera. Los
repositorios de código como IMSL estaban repletos de funciones para cálculos numéricos. Los usuarios pasaban datos a
estas funciones en largas listas de parámetros, que en ocasiones incluían no solo las estructuras de datos de entrada
sino también de salida. (IMSL ha seguido evolucionando a lo largo de los años y ahora hay una versión disponible para
los desarrolladores de Java).
El código se puede mejorar fácilmente. Para empezar, ¿por qué colocar los datos y las funciones que
operan en esos datos en módulos separados? Las clases de Java permiten agrupar datos y métodos:
Poner los elementos de datos de posición y el código de implementación para calcular la distancia
y el rumbo en la misma clase evita la necesidad de unaPositionUtility clase. AhoraPosición
comienza a parecerse a una verdadera clase orientada a objetos. El siguiente código utiliza esta
nueva versión que agrupa los datos y los métodos:
La salida es idéntica a la anterior y, lo que es más importante, el código anterior parece más natural.
La versión anterior pasó dosPosición objetos a una función en una clase de utilidad separada para
calcular la distancia y el rumbo. En ese código, calcular el encabezado con la llamada al método
util.heading (myHouse, coffeeShop) no indicó claramente la dirección del cálculo. Un desarrollador
debe recordar que la función de utilidad calcula el rumbo desde el primer parámetro al segundo.
Colocando los métodos que utilizanPosición datos de clase en elPosición la clase en sí hace curry las
funcionesdistancia yrumbo posible. Cambiar la estructura de llamada de las funciones en
de esta forma es una ventaja significativa sobre los lenguajes procedimentales. ClasePosición ahora representa un
tipo de datos abstracto que encapsula los datos y los algoritmos que operan sobre esos datos. Como tipo definido por
el usuario,Posición Los objetos también son ciudadanos de primera clase que disfrutan de todos los beneficios del
sistema de tipos de lenguaje Java.
La función de lenguaje que agrupa los datos con las operaciones que se realizan en esos datos es la
encapsulación. Tenga en cuenta que la encapsulación no garantiza ni la protección de datos ni el
ocultamiento de información. La encapsulación tampoco garantiza un diseño de clase cohesivo. Para lograr
esos atributos de diseño de calidad se requieren técnicas más allá de la encapsulación proporcionada por el
lenguaje. Tal como está implementado actualmente, classPosición no contiene datos y métodos superfluos o
no relacionados, peroPosición expone amboslatitud ylongitud en forma cruda. Que permite a cualquier
cliente de clasePosición para cambiar directamente cualquier elemento de datos internos sin ninguna
intervención porPosición. Claramente, la encapsulación no es suficiente.
Programación defensiva
Para investigar más a fondo las ramificaciones de exponer elementos de datos internos, suponga que
decido agregar un poco de programación defensiva aPosición restringiendo la latitud y la longitud a
rangos especificados por GPS. La latitud se encuentra en el rango [-90, 90] y la longitud en el rango
(-180, 180]. La exposición de los elementos de datoslatitud ylongitud enPosición' La implementación
actual hace que esta programación defensiva sea imposible.
Haciendo atributos de latitud y longitudprivado miembros de datos de la clasePosición y agregar métodos simples de
acceso y mutación, también llamados comúnmente captadores y definidores, proporciona un remedio simple para
exponer elementos de datos sin procesar. En el código de ejemplo a continuación, los métodos de establecimiento
filtran adecuadamente los valores internos delatitud ylongitud. En lugar de lanzar una excepción, especifico realizar
aritmética de módulo en los valores de entrada para mantener los valores internos dentro de los rangos especificados.
Por ejemplo, intentar establecer la latitud en 181.0 da como resultado una configuración interna de -179.0 paralatitud.
El siguiente código agrega métodos getter y setter para acceder a los miembros de datos
privadoslatitud ylongitud:
Usando la versión anterior dePosición requiere solo cambios menores. Como primer cambio, dado que
el código anterior especifica un constructor que toma dosdobleargumentos, el constructor
predeterminado ya no está disponible. El siguiente ejemplo usa el nuevo constructor, así como los
nuevos métodos getter. La salida sigue siendo la misma que en el primer ejemplo.
System.out.println
("De mi casa en (" +
myHouse.getLatitude () + "," + myHouse.getLongitude () +
") a la cafetería en (" +
coffeeShop.getLatitude () + "," + coffeeShop.getLongitude () +
") es una distancia de" + distancia +
"en un título de" + título + "grados".
);
Elegir restringir los valores aceptables delatitud ylongitud a través de métodos de establecimiento es
estrictamente una decisión de diseño. La encapsulación no juega ningún papel. Es decir, la encapsulación,
como se manifiesta en el lenguaje Java, no garantiza la protección de los datos internos. Como
desarrollador, eres libre de exponer los aspectos internos de tu clase. No obstante, debe restringir el
acceso y la modificación de elementos de datos internos mediante el uso de métodos getter y setter.
La protección de los datos internos es solo una de las muchas preocupaciones que impulsan las decisiones de diseño además
de la encapsulación del lenguaje. El aislamiento para cambiar es otro. La modificación de la estructura interna de una clase no
Por ejemplo, ya señalé que el cálculo de la distancia en clasePosiciónno indicó unidades. Para
ser útil, la distancia reportada de 6.09 desde mi casa a la cafetería claramente necesita una
unidad de medida. Puede que sepa la dirección a tomar, pero no sé si caminar 6,09 metros,
conducir 6,09 millas o volar 6,09 mil kilómetros.
Análisis de las modificaciones necesarias para agregar unidades a clasePosición revela otro posible defecto
de diseño: no se ha especificado la geometría a utilizar. Puede calcular la distancia y viajar entre dos
puntos cualesquiera de la superficie de la Tierra de varias formas. Dependiendo de la necesidad de
precisión, las distancias dentro de los 50 kilómetros podrían usar las coordenadas cartesianas locales de la
geometría del plano. Estos cálculos simples pueden ser suficientes para ubicaciones dentro de la misma
área, pero calcular la distancia y viajar de San Francisco a París requiere cálculos geométricos más difíciles
a lo largo de un gran círculo. Y realizar estudios de terremotos midiendo la distancia y la dirección entre
ubicaciones precisas a lo largo de la falla de San Andrés en California puede requerir el uso de geometría
elíptica hiperprecisa, incluso para ubicaciones dentro de los 10 kilómetros entre sí.
En lugar de incluir directamente todas estas opciones de dominio enPosición, Agrego variables de referencia
de tipoUnidades yGeometría. Los objetos a los que hacen referencia estas variables manejan los detalles
relacionados con las unidades y la geometría. No muestro el código real para la definición de interfaces.
Unidades yGeometría ni ninguna clase concreta que implemente estas interfaces. Baste decir que hay cuatro
implementaciones para las unidades:
● Kilómetros
● Millas náuticas
● EstatuaMillas
● Radianes
● Geometria plana
● Geometría esférica
● Geometría Elíptica
Ahora, para el descubrimiento más importante y potencialmente dañino: mientras se crean las diversas
clases de geometría, se determina que para delegar el trabajo a estos objetos, la clasePosiciónno debe
mantener elementos de datoslatitud ylongitud, pero en su lugar debe representar la ubicación interna
utilizando ángulos de coordenadas esféricastheta yphi. No me desviaré del motivo. Independientemente
de por qué es necesario tal cambio, el cambio en sí mismo puede resultar doloroso o imposible siPosición
Los clientes acceden directamente a los datos internos. Antes de examinar las ramificaciones de estos
cambios, veamos la versión actualizada dePosición que presenta las nuevas variables de referencia:
Note que aunquePosición ya no mantiene elementos de datos internoslatitud ylongitud, los métodos
getter y setter correspondientes permanecen. Esa estabilidad aísla los objetos del cliente del cambio
interno. Al hacer que los captadores y definidores accedan a los atributos de latitud y longitud, los clientes
ni siquiera necesitan saber sobre el cambio. Las únicas modificaciones necesarias en el siguiente uso del
código del nuevoPosición la clase se refiere a la adición de las unidades y los atributos de geometría:
================================================
=================
Usando geometría plana, desde mi casa en (36.538611, -121.7975)
a la cafetería en (36.539722, -121.907222) es una distancia de
9.79675938972254 Kilómetros con rumbo 270.58013375337254
grados.
Usando geometría esférica, desde mi casa en (36.538611, -121.7975)
a la cafetería en (36.539722, -121.907222) es una distancia de
6.0873776351893385 Millas estatutarias en un rumbo de 270.7547022304523
grados.
================================================
=================
La salida incluye tanto la geometría utilizada para los cálculos como las unidades de distancia. La distancia
relativamente pequeña produce una diferencia insignificante, aunque no imperceptible, entre el uso de
geometrías planas y esféricas. Los rumbos calculados difieren ligeramente, y la comparación de las distancias
utilizando el factor de conversión de 1,61 kilómetros por milla muestra que las distancias también varían
ligeramente.
Carros y cuervos
Ahora que sé que la cafetería está a 6,09 millas al oeste de mi casa, decido conducir hasta allí. Con la desesperada necesidad
de una dosis de café, me subo a mi coche y me preparo para dirigirme hacia el oeste. Desafortunadamente,
que me lleva directamente a través de la parte trasera de mi garaje, a través del césped trasero y en un barranco de 20
metros de profundidad. ¡Hasta aquí los servicios basados en la ubicación! Necesitaría más que café para salir de ese
lío.
Aunque me parezca genial la idea, el municipio en el que vivo aún no ha aprobado una carretera que conecte
directamente mi casa con la cafetería. Entonces, si quiero Java, tendré que modificar mi ruta planificada.
PosiciónLa información de distancia y dirección funciona bien para los cuervos, pero no tanto para los
automóviles.
Decido construir una clase que represente la ruta real de conducción entre mi casa y la cafetería. El
camino consta de una serie de segmentos que conectan puntos a lo largo del camino. Así que clase
Ruta mantiene elPosición objetos necesarios para definir una ruta. Un diseño de primer corte (y
ciertamente no completo) se ve así:
Abundan los problemas en el diseño simple de primera clase de la clase.Ruta. Desde una perspectiva de
encapsulación y ocultación de información, el método de accesogramoetPositions (), que devuelve el
matriz dePosición objetos usados dentro de la claseRuta, resulta potencialmente problemático. Aunque
claseRuta encapsula la matriz, no protege la decisión de diseño que resultó en el uso de una matriz. Es
decir, devolviendo la matriz interna dePosiciónobjetos deRuta ha expuesto la decisión de utilizar una
matriz. Esto es particularmente atroz cuando el diseño usa un nombre de método como
getPositionsArray (). Si en un momento posterior el diseño se cambia a unArrayList,RutaLos clientes están
expuestos al cambio. Puede crear y devolver una matriz primitiva desde elArrayList, pero el problema
persiste. La elección de la estructura de datos interna utilizada para administrar la ruta no debería afectar
a los clientes externos. Esa es una clara diferencia entre la encapsulación y el ocultamiento de
información.
Más problemas para ocultar información acechan en este diseño. El métodogramo etPosition (int) expone
los elementos de datos internos reales mantenidos porRuta. Cualquier cliente puede obtener una referencia a
cualquiera de losPosición objetos a lo largo de la ruta y librementecambiar el estado de esoPosición objeto.
Para apreciar las ramificaciones de esta exposición, considere una posible clase
Rutaimplementación del métododistancia (), que calcula la distancia acumulada en todos los segmentos
de la ruta. Suponga que la implementación itera sobre la internaPosición objetos y utiliza cada objeto
para calcular la distancia al siguientePosición. El cálculo claramente requiere el uso de un solo tipo de
unidad de distancia. Para hacer cumplir este requisito, el métodoañadir (posición) en el código siguiente
establece uniformemente las unidades de todas las entradasPosición objetos.
Y ahora para la vulnerabilidad total de lagetPosition (int) exposición al método: aunque todos losPosición
objetos agregados a la claseRuta podrían tener sus unidades correctamente configuradas cuando se
agregan a la ruta, cualquier objeto cliente podría obtener unPosiciónobjetar y configurar las unidades a lo
que elija el cliente,sin informandoRuta. El daño potencial al calcular el resultado en el métododistancia ()
resulta particularmente desconcertante. distancia () podría sin saberlo y fácilmente agregar kilómetros a
millas terrestres.
ClaseRuta aún no está completo, pero he reforzado el diseño ocultando adecuadamente las decisiones
de diseño tomadas hasta ahora.
Conclusión
La encapsulación es una construcción de lenguaje que facilita la agrupación de datos con los métodos que
operan en esos datos. La ocultación de información es un principio de diseño que se esfuerza por proteger a
las clases de clientes del funcionamiento interno de una clase. La encapsulación facilita, pero no garantiza, la
ocultación de información. Unir los dos en un concepto evita una comprensión clara de cualquiera de ellos.
La manifestación de encapsulación en lenguaje Java ni siquiera asegura objetos básicos orientados a objetos. El
argumento no es necesariamente que debería, solo que no es así. Los desarrolladores de Java pueden crear
alegremente bolsas de datos en una clase y colocar funciones de utilidad que operan en esos datos en una clase
separada. Entonces, como primera regla:
Regla de encapsulación 1: coloque los datos y las operaciones que se realizan en esos datos en la
misma clase
Esta práctica estándar crea clases que se adhieren a los principios de los tipos de datos abstractos.
Pero quieres más de tus objetos; desea que representen entidades cohesivas y viables. Una
segunda regla se refiere a la forma de elegir los datos y las operaciones para encapsular:
Como ha visto en los muchos ejemplos anteriores, la función de encapsulación del lenguaje Java no es suficiente
para garantizar un diseño de clase sólido. El principio de ocultación de información estipula que protege a los
clientes de un objeto de las decisiones de diseño interno que se toman para esa clase de objetos. Entonces,
como primera regla para ocultar información:
Hacer todos los elementos de datosprivado y use getters y setters. No se engañe creyendo que no se producirá ningún
daño al acceder directamente a los elementos de datos internos de un objeto. Incluso si solo codifica contra esos
componentes internos, aún existe una vulnerabilidad futura. No puede predecir cuándo podría necesitar cambiar la
naturaleza de los datos internos, y el acoplamiento frágil con los objetos del cliente suena desconcertante cuando se
rompe.
Vaya un paso más allá cuando oculte decisiones de diseño relacionadas con datos internos. Cuando sea posible,
ni siquiera revele si un atributo está almacenado o derivado. Los objetos del cliente no necesitan conocer la
diferencia. Un atributo es una cualidad o característica inherente a una clase de objetos. Desde la perspectiva
del cliente, los atributos de los objetos corresponden a responsabilidades, no a la estructura interna de los
datos. Eso nos lleva a la siguiente regla:
Regla de ocultación de información 2: no exponga la diferencia entre los datos almacenados y los datos
derivados
Por ejemplo, un objeto cliente solo necesita saber que un objeto tiene un atributo develocidad. Utilice un
método de acceso llamadovelocidad () ogetSpeed () en lugar decalcularSpeed (). Si el valor se almacena o
se deriva es una decisión de diseño que es mejor mantener oculta.
Como corolario de las dos primeras reglas de ocultación de información, coloco todos los datos internos al final
del texto de la clase, después de los métodos que operan con los datos. Cuando examino una clase para
comprender su código, mirar primero sus datos internos me lleva a hacer las preguntas iniciales incorrectas. Me
esfuerzo por comprender las responsabilidades de una clase antes de preocuparme por
cualquier detalle de la estructura de datos. Colocar datos después de los métodos en el texto de la clase me recuerda, cada
vez que miro el código, pensar primero en el comportamiento de una clase, no en su estructura.
Los clientes deben permanecer aislados de las decisiones de diseño que impulsan la selección de la
estructura de clases interna. Por ejemplo, un cliente no debe saber si una matriz primitiva o una
ArrayList se utiliza para mantener una colección interna de objetos. La estructura interna es
particularmente evidente mediante el uso de nombres de métodos comogetDataArray () o
getTreeMap ().
No permita que los clientes conozcan o afecten de manera invisible los detalles de implementación de una clase. Por
ejemplo, un cliente no debería poder alterar el resultado de un cálculo interno cambiando el estado de los objetos
utilizados en ese cálculo supuestamente oculto.
Aunque no es una lista exhaustiva, esas reglas ayudan a separar el concepto de encapsulación proporcionado por
el lenguaje de la ocultación de información proporcionada por las decisiones de diseño. Cada regla es bastante fácil
de seguir y cada una ayudará a crear clases que no solo sean más resistentes a los cambios, sino que también sean
más fáciles de usar por los objetos del cliente.
Wm. Paul Rogers is a senior engineering manager and application architect at Lutris
Technologies, where he builds computer solutions utilizing Enhydra, the leading open source
Java/XML application server. He began using Java in the fall of 1995 in support of oceanographic
studies conducted at the Monterey Bay Aquarium Research Institute, where he led the charge in
utilizing new technologies to expand the possibilities of ocean science research. Paul has been
using object-oriented methods and technologies for more than nine years.