Publicado el 6 de Septiembre del 2017
514 visualizaciones desde el 6 de Septiembre del 2017
199,0 KB
19 paginas
Creado hace 20a (30/09/2003)
Clase 3: Desacoplamiento II
En la clase anterior, hablamos de la importancia de las dependencias en el diseño de un
programa. Un buen lenguaje de programación le permitirá expresar las dependencias entre las
partes y controlarlas, evitando que surjan dependencias no deseadas.
En esta clase, veremos cómo se pueden utilizar los elementos de Java para expresar y manejar
dependencias. Estudiaremos también una variedad de soluciones para un problema simple de
codificación, haciendo especial hincapié en el papel de las interfaces.
3.1 Repaso: Diagramas de dependencia de módulos
Comencemos dando un breve repaso a los diagramas de dependencia de módulos (MDD) que
vimos en la última clase. Un diagrama de dependencia de módulos (MDD) muestra dos tipos
de partes en un programa: partes de la implementación (clases de Java), que aparecen como
recuadros con una única raya adicional en la parte superior, y partes de la especificación, que
se presentan como recuadros con una raya tanto en la parte superior como en la inferior. Las
organizaciones de partes en grupos (como los paquetes de Java) pueden mostrarse como
contornos que contienen partes de un programa, siguiendo el estilo de un diagrama de Venn.
Una flecha simple con la punta abierta conecta la parte de la implementación A con la parte de
la especificación S, e indica que el significado de A depende del significado de S. Dado que la
especificación S no puede tener por sí misma un significado que dependa de otras partes, se
asegura que el significado de una parte se puede determinar desde esa misma parte y desde las
especificaciones de las que ésta depende, sin tener que recurrir a ningún otro dato. Una flecha
de puntos que vaya desde A hasta S es una dependencia débil; indica que A depende
únicamente de la existencia de una parte que satisfaga la especificación S, pero que en
1
realidad no tiene dependencia de ninguno de los detalles de S. Una flecha de punta cerrada
que vaya desde una parte de la implementación A a una parte de la especificación S indica que
A satisface a S: su significado se ajusta al de S.
Dado que las especificaciones son tan imprescindibles, debemos asumir en todo momento que
están presentes. La mayoría de las veces, no dibujaremos partes de la especificación de forma
explícita y, de este modo, una flecha de dependencia entre dos partes de implementación A y
B deberá interpretarse como abreviatura de una dependencia de A para la especificación de B,
y como una flecha de conformidad de B para su especificación. Mostraremos las interfaces de
Java explícitamente como partes de la especificación.
3.2 Java Namespace (sistema de denominación)
Al igual que cualquier trabajo escrito de gran extensión, un programa también se beneficia del
hecho de estar organizado conforme a una estructura jerárquica. Cuando se intenta
comprender una gran estructura, suele resultar útil visualizarla de arriba abajo, comenzando
por los niveles más generales de la estructura y prosiguiendo hasta llegar a los detalles más
concretos. El namespace (sistema de denominación) de Java soporta esta estructura
jerárquica, lo que supone otra ventaja importante: diferentes componentes pueden utilizar los
mismos nombres para sus subcomponentes, con significados locales distintos. En el contexto
del sistema como un todo, los subcomponentes llevarán nombres que estén condicionados
por los componentes a los que pertenecen, de modo que no habrá confusión. Esto es
fundamental porque permite que los programadores trabajen de forma autónoma, sin
preocuparse por conflictos de denominación.
El sistema de denominación de Java funciona del modo que a continuación exponemos. Los
componentes considerados clave son clases e interfaces, y poseen denominaciones de
métodos y campos nombrados. Las variables locales (dentro de los métodos) y los argumentos
de un método también poseen su nombre. Cada nombre en un programa de Java tiene un
2
alcance: una parte del texto del programa para la que el nombre es válido y se halla asociado
al componente. Los argumentos de un método, por ejemplo, poseen el mismo alcance del
método; los campos tienen el alcance de la clase y, en algunas ocasiones, un alcance aún
mayor. Se puede usar el mismo nombre para referirse a cosas distintas cuando no existe
ambigüedad. Por ejemplo, es posible utilizar el mismo nombre para un campo, un método y
una clase; consúltense las especificaciones del lenguaje Java para ver los ejemplos.
Un programa de Java se organiza por paquetes. Cada clase o interfaz posee su propio fichero
(haciendo caso omiso de las clases internas, que no trataremos). Los paquetes se hallan
reflejados en la estructura del directorio. Al igual que los directorios, los paquetes pueden
estar anidados en estructuras de profundidad arbitraria. Para organizar un código en paquetes,
deben hacerse dos cosas: indicar al comienzo de cada fichero a qué paquete pertenece la clase
o interfaz, y organizar los archivos físicamente dentro de la estructura de un directorio para
que se ajusten a la estructura del paquete. Por ejemplo, la clase djn.browser.Protocol estaría
en un fichero llamado Protocol.java en el directorio djn/browser.
Podemos mostrar esta estructura en nuestro diagrama de dependencia. Las clases e interfaces
constituyen las partes entre las que se muestran las dependencias. Los paquetes se presentan
como contornos que engloban estas clases e interfaces. A veces resulta conveniente ocultar las
dependencias exactas entre las partes de diferentes paquetes mostrando simplemente un arco
de dependencia al nivel del paquete. Una dependencia desde un paquete significa que alguna
clase o interfaz (o quizás varias) de ese paquete tiene una dependencia; una dependencia de
un paquete significa una dependencia de alguna clase o interfaz (o quizás varias) de ese
paquete.
3
3.3 Control de acceso
Los mecanismos de Java para el control de acceso permiten dirigir las dependencias. En el
texto de una clase, se puede indicar qué otras clases pueden tener dependencias de ella, e
incluso controlar, hasta cierto punto, la naturaleza de las dependencias.
Una clase declarada pública puede ser referida por cualquier otra clase; si no, puede ser
referida sólo por clases del mismo paquete. Por tanto, al lanzar este modificador, podemos
evitar dependencias de clase de cualquier clase que no pertenezca al paquete.
Los miembros de una clase –es decir, sus campos y métodos—se pueden marcar como
públicos, privados o protegidos. Un miembro público puede accederse desde cualquier parte.
Un miembro privado puede accederse únicamente desde dentro de la clase en la que el campo
o el método se ha declarado. Un miembro protegido puede accederse bien desde dentro del
paquete o bien desde fuera por una subclase de la clase en la que el miembro es declarado,
creando así un resultado muy peculiar, que consiste en que al marcar un miembro como
protegido, éste no se hace menos accesible, sino más.
4
No hay que olvidar que una dependencia de A sobre B indica en realidad una dependencia de
A sobre la especificación de B. Los modificadores de los miembros de B nos permiten
controlar la naturaleza de la dependencia al cambiar los miembros que pertenecen a la
especificación de B. Controlar el acceso a los campos de B ayuda a dar independencia de
representación, pero no siempre la garantiza (como veremos próximamente en este curso).
3.4 Lenguajes seguros
Una de las propiedades claves de un programa es que una parte únicamente debería depender
de otra si ésta la nombra. Esto puede parecer obvio, pero es de hecho una propiedad que sólo
se da en los programas escritos mediante los llamados “lenguajes seguros”. En un lenguaje
inseguro, el texto de una parte puede afectar al comportamiento de otra, sin que haya ningún
nombre compartido. Esto nos lleva a errores insidiosos que resultan muy difíciles de localizar,
y que pueden tener resultados desastrosos e imprevisibles.
Veamos cómo se producen estos errores. Piense en un programa escrito en C, en el que un
módulo (en C, sólo un fichero) actualiza un array. Un intento de fijar el valor de un elemento
del array más allá de los límites de éste no dará resultado en algunas ocasiones, ya que
provocará un fallo de memoria, yendo más allá del área de memoria asignada al proceso. Sin
embargo, y por desgracia, la mayoría de las veces el intento sí dará resultado, y éste consistirá
en que se sobrescribirá una parte arbitraria de memoria; arbitraria porque el programador no
sabe cómo el compilador dispuso de la memoria del programa, y no puede predecir qué otra
estructura de datos se ha visto perjudicada. A consecuencia de ello, una actualización del
array puede afectar al valor de una estructura de datos con el nombre d que se ha declarado en
un módulo diferente y no posee ni siquiera un tipo en común con a.
Los lenguajes seguros evitan estos efectos mediante la combinación de distintas técnicas. La
comprobación dinámica de los límites del array impide que se produzca este tipo de
actualización que acabamos de mencionar; en Java, se lanzaría una excepción. La
5
administración automática de la memoria asegura que ésta no pueda ser reciclada y
posteriormente reutilizada de modo erróneo. Ambas técnicas parten de la idea básica de
paradigma de tipos fuertes, que asegura que un acceso que sea declarado a un valor de tipo t
en el texto del programa sea siempre un acceso a un valor de tipo t en tiempo de ejecución.
No existe riesgo de que el código diseñado para un array pueda ser aplicado por error a una
cadena o número entero.
Los lenguajes seguros han estado circulando desde 1960. Entre los más famosos se incluyen
Algol-60, Pascal, Modula, LISP, CLU, Ada, ML y ahora Java. Res
Comentarios de: Clase 3: Desacoplamiento II (0)
No hay comentarios