4. La herencia, más aumento de la ambigüedad

advertisement
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
4. La herencia, más aumento de la ambigüedad
Índice
4.
La herencia, más aumento de la ambigüedad ....................................................... 103
Sobre el capítulo ................................................................................................... 104
Motivaciones .................................................................................................... 104
Objetivo ............................................................................................................ 104
Contenido ......................................................................................................... 104
4.1
La herencia ................................................................................................... 105
4.2
El polimorfismo ............................................................................................ 106
4.3
La herencia como taxonomía........................................................................ 107
4.4
Curiosidades biológicas de la herencia software .......................................... 108
4.5
La herencia y la evolución (equivocada) del software ................................. 109
4.6
Delegar en vez de heredar ............................................................................ 112
4.7
El principio de sustitución de Liskov ........................................................... 114
4.8
La evolución segura del software ................................................................. 115
4.9
El aporte del principio de sustitución, la ambigüedad .................................. 117
4.10 Condiciones del principio de sustitución ...................................................... 118
4.11 Contraejemplo de la herencia. El cuadrado no es un rectángulo .................. 119
4.12 Las clases abstractas ..................................................................................... 122
4.13 Las clases generales, una solución alternativa, pero… ................................ 124
4.14 Las clases particulares, beneficios y problema ............................................. 126
4.15 La ambigüedad, solución al problema de la diversidad................................ 127
4.16 La ambigüedad es la clave, no la división .................................................... 128
4.17 La herencia múltiple ..................................................................................... 128
4.18 Aproximación al patrón adaptador ............................................................... 131
4.19 La herencia vista desde el código, un ejemplo ............................................. 131
4.20 El polimorfismo en Java ............................................................................... 136
4.21 Ejercicio ........................................................................................................ 143
Una solución ......................................................................................................... 144
4.22 Otro ejercicio ................................................................................................ 147
La búsqueda de la plasticidad, un cambio del diseño ........................................... 148
La solución del rectángulo.................................................................................... 151
Bibliografía ........................................................................................................... 152
103
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
Sobre el capítulo
Motivaciones
La herencia ha sido y es un canto de sirena en los objetos. Quien lo escucha con
oídos ingenuos se hunde con el sistema. Parece que la herencia es la fuente de ahorro de
código, de la facilidad de modificación y extensión de los programas, pero realmente es
lo contrario, salvo que se utilice con el papel y la forma adecuada. El papel adecuado de
la herencia es como medio de aumento de la ambigüedad y la forma adecuada de uso es
mediante el polimorfismo.
Objetivo
El objetivo del presente capítulo es que los alumnos comprendan:
1. El concepto de herencia, su papel favorable y sus papeles perjudiciales
2. El concepto de polimorfismo
3. Que es mejor delegar que heredar
4. El principio de sustitución de Liskov, como forma segura y útil de la
herencia
5. Las clases abstractas
6. Que la ambigüedad es la clave, no la división
Contenido
La primera parte profundiza en la herencia, sus aspectos buenos y malos, y
recomienda que es preferible delegar que heredar. Se enuncia también el concepto de
polimorfismo.
La segunda parte estudia con detalle el principio de sustitución de Liskov, sus
condiciones y cómo un cuadrado no es un rectángulo, en términos de la herencia
software.
La tercera parte se dedica a las clases abstractas; a la ambigüedad como solución
de la diversidad y, en general, como solución más poderosa que el limitado principio de
divide y vencerás.
La cuarta parte discute la herencia múltiple, sus efectos nefastos actuales y las
restrictivas condiciones donde es favorable, por ejemplo, en el patrón adaptador.
104
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
4.1 La herencia
La herencia es uno de los cantos de sirenas del enfoque de objetos. Se dice
mucho de sus favores y muy poco de sus peligros. Por esta causa primero se estudiará la
definición y los peligros, y después se verá la forma de obtener los favores de la
herencia.
Desde el punto de vista formal, [Booch 94] establece que:
La herencia es una relación entre clases donde una clase comparte la estructura o
comportamiento definido en otra clase (herencia simple) o en más clases
(herencia múltiple). La herencia define una jerarquía “es-un” entre clases, en la
cual una subclase hereda de una o más clases generalizadas; una subclase
típicamente especializa su superclase añadiendo o redefiniendo la estructura y el
comportamiento.
La Figura 4. 1 ilustra el mecanismo de herencia simple.
A
a1
a2
a3
herencia
superclase
m1
m2
m3
C
a1
a2
a3
a4
m1
m2
m3
m4
B
a1
a2
a3
subclases
m1
m2
m3
Figura 4. 1 La herencia
En la Figura 4. 1, la clase B comparte las propiedades de la clase A, pero
redefine a2 y m2. La clase C, también, comparte los atributos de la clase A, pero añade
las propiedades a4 y m4. Las subclases no pueden rechazar ninguna propiedad de sus
105
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
superclases. Se acostumbra a escribir sólo lo nuevo y se omiten las propiedades sin
cambio. La relación de herencia se establece desde las subclases hacia la superclase. Es
decir, B y C indican explícitamente que son subclases de A en cada una de sus
declaraciones. La palabra subclase sólo denota que es una clase que hereda y la palabra
superclase sólo denota que es una clase de la que se hereda. La superclase y la subclase
reciben también reciben otros nombres, por ejemplo: clase base y clase derivada
respectivamente.
Desde el punto de vista del código, los objetos de la subclase B son también
objetos de la clase A, aunque dos de sus propiedades (a2, m2) tienen cualidades distintas
a los objetos de la clase A. De modo semejante, los objetos de la subclase C son también
objetos de la clase A.
4.2
El polimorfismo
El polimorfismo es una de las cualidades importantes del enfoque de objetos por
su aporte de ambigüedad en el diseño. Está asociado con el mecanismo de herencia y
permite que la operación definida por una misma cabecera (signatura) sea implementada
de maneras distintas.
Se denomina polimorfismo a la capacidad de una operación para manifestar un
comportamiento diferente dependiendo del objeto que la ejecuta. Por ejemplo, la
operación m2 es polimórfica porque su comportamiento depende de si la ejecuta
un objeto de la clase A o un objeto de la clase B.
Los clientes de una operación polimórfica la invocan a través del mismo
mensaje, por ejemplo v.m2, pero el comportamiento depende del objeto que exprese la
variable v. A continuación se muestra el pseudocódigo y resultado de una operación que
utiliza el servicio polimórfico m2 ofrecido por objetos de las clases A, B y C.
v es A
declaración de la variable v
v ← O:A
se asigna a la variable v un objeto de la clase A
v:m2
mensaje al objeto asignado a v para que ejecute m2
Se ejecuta la operación m2 de la clase A
106
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
v es A
declaración de la variable v
v ← O:C
se asigna a la variable v un objeto de la clase C
v.m2
mensaje al objeto asignado a v para que ejecute m2
Se ejecuta la operación m2 de la clase A porque la clase C lo utiliza sin
redefinición.
v es A
declaración de la variable v
v ← O:B
se asigna a la variable v un objeto de la clase B
v.m2
mensaje al objeto asignado a v para que ejecute m2
Se ejecuta la operación m2 de la clase B porque la clase B ha redefinido m2.
v es A
declaración de la variable v
v ← O:C
se asigna a la variable v un objeto de la clase C
v.m4
mensaje al objeto asignado a v para que ejecute m2
Equivocación: La clase A carece del método m2.
En principio, cualquier operación puede ser polimórfica a través del mecanismo
de herencia, salvo las operaciones de creación de objetos que por su tarea específica, no
pueden ser polimórficas. Un constructor crea objetos de una clase y no de otra. Más
adelante se volverá a tratar este tema.
4.3
La herencia como taxonomía
Desde el punto de vista conceptual se podría decir que los objetos de B y C son
variantes de A. Esta idea permite asociar la herencia con una relación jerárquica “es un”.
Es decir, como un instrumento de clasificación, al estilo taxonómico de las ciencias
naturales. Abundan los ejemplos que ilustran la herencia a través de modelos
taxonómicos: la clase perro es una subclase de la clase mamífero. Pero, en general,
constituye un ejemplo poco afortunado. Primero, porque cualquier clasificación es
subjetiva y siempre admite objeciones, de manera que al introducir clasificaciones en el
107
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
software se introducen fuentes de cambio. Segundo, porque las clasificaciones usuales
pueden ser contraproducentes para el sistema software como sucede con el clásico
ejemplo del cuadrado y el rectángulo, discutido más adelante.
4.4
Curiosidades biológicas de la herencia software
El sabor biológico del mecanismo software denominado herencia se hace
explícito en su propio nombre. Algunos lenguajes se diferencian y le llaman extensión
(“extends”) o implementación (“implements”) según sea el caso. Hay varias
curiosidades asociadas con este mecanismo.
La herencia software parece un mecanismo “sexuado” puesto que un
descendiente puede tener varios progenitores. Vista así, la herencia simple se
corresponde con el fenómeno biológico denominado partenogénesis donde un solo
progenitor es capaz de crear. Los descendientes tienen, en general, el mismo sexo que el
progenitor. En la práctica software es una curiosidad sin trascendencia, aunque de valor
nemotécnico como se verá después.
Otra curiosidad, pero esta vez clave, de la “herencia” software es que los
“progenitores” software desconocen a sus retoños, al revés de la mayoría de los
humanos. En el enfoque de objetos, las hijas son las que reconocen a sus madres. La
nueva clase se declara hija de alguna clase ya existente como sucede con el pájaro cuco
insertado en nido ajeno. En otros casos los recién nacidos reconocen como progenitor a
cualquiera que esté presente en el momento de su nacimiento. Si las “madres” software
tuviesen que declarar a las hijas, habría que modificar la declaración (código) de la
madre cada vez que se creara una hija. Por este motivo, la referencia hereditaria es de
descendientes hacia ascendientes, en el software de objetos.
La “herencia” software es un reflejo extremo del modelo de Lamarck donde se
heredan los cambios somáticos. En la “herencia” software, si al progenitor (superclase)
se le quita o cambia una propiedad todos sus descendientes pierden o cambian,
inmediatamente, esa propiedad. Lamarck era contemporáneo de Darwin, pero de ideas
diferentes. Esta otra curiosidad también es clave en el software. Si la “madre” software
adelgaza (se le quita alguna propiedad) todas las descendientes (hijas, nietas,…
choznas,…) nacidas o por nacer adelgazarán inmediatamente. En fin que, cualquier
108
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
modificación en los predecesores se reflejará en los futuros sucesores y se propagará a
los ¡que ya existen!, hecho que no sucede en ninguna herencia natural.
La propagación de los cambios puede parecer una virtud de la herencia, pero
realmente es una catástrofe. En primer lugar porque cualquier clase puede ser madre de
muchos descendientes sin saberlo; no hay referencia a ellos. La búsqueda de los
descendientes es un problema laborioso, sobre todo si los esquemas no existen, están
desactualizados o no se dispone de un medio automático para hacerlo. En segundo
lugar, porque cualquier cambio afecta a quienes utilizan esa clase y localizar a los
afectados es una tarea mucho más difícil de realizar. Mientras más descendientes, mayor
será el problema. La situación es equivalente a una epidemia de cambios y fallos.
4.5
La herencia y la evolución (equivocada) del software
El símil biológico inspira a usar la herencia como una vía de evolución del
software, que se ajusta a las nuevas necesidades, a través de la creación de clases hijas
que heredan las propiedades de las clases existente y las modifican o añaden otras
propiedades nuevas. Parece que la herencia permite ahorrar código, no tocar lo que ya
funciona, y además, permite que se puedan cambiar muchas clases cambiando una sola.
Son ideas atractivas, como las sirenas.
La Figura 4. 2 muestra un árbol jerárquico de herencia cuya raíz es la clase
Madre. De ella derivan las clases Hija 1 e Hija 2, que a su vez tienen hijas: la clase
Nieta 1 y Nieta 2, Para compactar la figura, los atributos y los métodos se han separado
por comas, aunque cada uno debe ir escrito en una línea, según la notación de UML.
109
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
Madre
a1,a2,a3
herencia
m1,m2,m3
soy hija de
Hija1
Hija2
a4
a2
m4
m2
Nieta1
Nieta2
soy hija de
a5, a6
m3, m5
m2
Figura 4. 2 Ejemplo de herencia
En la figura sólo aparecen los atributos y los métodos añadidos o modificados
con respecto a los predecesores. Por ejemplo, la clase Hija 1 añade el atributo a4 y el
método m4. La clase Hija 2 modifica el atributo a2 y el método m2, y los declara para
diferenciarlos de los predecesores. La clase Nieta 1 añade los atributos a5 y a6, y el
método m5. Pero también, modifica el método m3 de la clase Madre, es decir de su
abuela. Y por último, la clase Nieta 2 modifica el método m2 de la clase Hija 2, su
madre. La descripción ya es confusa.
110
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
La facilidad que parece ofrecer la herencia para la evolución del sistema
software se puede evaluar en la práctica con la Figura 4. 3, que se ha repetido en la
Figura 4. 2 por comodidad.
Madre
a1,a2,a3
herencia
m1,m2,m3
soy hija de
Hija1
Hija2
a4
a2
m4
m2
Nieta1
Nieta2
soy hija de
a5, a6
m3, m5
m2
Figura 4. 3 Repetición de la Figura 4. 2
Al ver en el código la clase Nieta 2, se puede pensar que sólo tiene la propiedad
m2, pero no es así. Como esta clase es hija de Hija 2, habrá que buscar y localizar a la
clase madre para saber qué propiedades hereda de ella. Una vez localizada, se sabe que
Nieta 2 tiene la propiedad m2 (propia, porque redefine a la madre) y la propiedad a2,
según la madre, Hija 2. Pero, la historia continua. Hija 2 es hija de Madre. Entonces,
Nieta 2 no está casi vacía como parece. Nieta 2 tiene las propiedades: m2, propia; a2 de
Hija 2 (su madre, que a su vez redefine a Madre, la madre de Hija 2). Y tiene también,
las propiedades a1, a3, m1 y m3 de Madre porque Hija 2 no las redefine.
En fin, con apenas tres clases se pudiera exclamar: “Madre, el drama padre”,
título de un delicioso enredo teatral de Jardiel Poncela. Mientras más genealogía, más
calvario y mayor probabilidad de equivocaciones. Por otra parte, si se quiere reutilizar
alguna clase hija habrá que llevarse a la nueva aplicación, todas las clases antecesoras.
111
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
Resumiendo, la herencia es un potente instrumento de trabajo, pero peligroso
porque puede ocasionar graves efectos negativos según se ha visto. La herencia ofrece
su mayor utilidad y menor riesgo cuando se emplea para ampliar la capacidad de
expresar ambigüedad del diseño, según se deriva del principio de Liskov. Para otro tipo
de uso es mejor delegar que heredar.
4.6
Delegar en vez de heredar
Esta idea es vieja en el software, ya tiene más de quince años. [Rumbaugh 95]
utilizó el ejemplo de una pila y una lista, pero se puede usar el ejemplo de cliente y
persona. Figura 4. 4
Delegar en vez de heredar
Persona
Persona
Cliente
Cliente
Figura 4. 4 Delegar en vez de heredar
Una situación concreta puede sugerir el diseño de la derecha (herencia) porque
los clientes del negocio son personas. Los objetos de la clase Cliente son también de la
clase Persona en virtud de la relación “es un” de la clase Cliente hacia la clase Persona.
Sin embargo, para ser cliente no es imprescindible ser persona por tanto la relación
“cliente es una persona” resulta una relación circunstancial susceptible de cambio.
Mañana cliente puede ser una empresa o hasta un plutoniano, ahora que Plutón ha
dejado de ser un planeta (el eterno problema de las clasificaciones).
112
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
No conviene utilizar la herencia para relacionar Cliente y Persona por varias
razones. Primero, porque es previsible que cambie y cambiará, según Murphy. Segundo,
porque los cambios son más engorrosos dadas las fuertes ligaduras entre Cliente y
Persona a causa de las propiedades que hereda Cliente de Persona. Tercero, porque
conceptualmente es una relación temporal y la herencia expresa una relación
permanente.
La relación de composición de entre las clases Cliente y Persona (una parte de
persona es la situación de cliente) reduce los inconvenientes citados porque una persona
siempre puede estar en la situación de cliente, al menos de forma potencial. Por tanto, es
una relación más estable, más próxima a la condición de permanencia que expresa la
composición, en consecuencia, hay menos posibilidad de cambio en la relación.
Además, de producirse cambios, serían menos engorrosos porque la ligadura de Cliente
hacia Persona es mucho más débil puesto que no hereda nada.
En vez de composición se pudiera usar una relación de asociación, pero la
composición enfatiza la pertenencia exclusiva del objeto cliente al objeto persona que lo
contiene. De este modo se debe asegurar que el objeto cliente de un objeto persona no
se comparta con otro objeto del sistema. Los lenguajes actuales no suministran esta
cualidad, si se quiere hay que implementarla.
113
Curso de OO dirigido por
la introducción de ambigüedad
4.7
La herencia, más aumento de la ambigüedad
El principio de sustitución de Liskov
Después del análisis de los muchos problemas de la herencia, el principio de
sustitución de Liskov, formulado hace casi dos décadas, ofrece un camino útil y
confiable para aprovechar los favores de la herencia.
Literalmente el principio define, en términos de una sustitución segura, cuando
una subclase es un subtipo de una superclase.
“Si para cada objeto O1 de tipo S hay un objeto O2 de tipo T tal que para todos
los programas P definidos en términos de T, el comportamiento [interno] de P
no cambia cuando O1 es sustituido por O2, entonces S es un subtipo de T.”
[Liskov 86]
La Figura 4. 5 ilustra la aplicación del principio. Cuando O1:S es sustituido por
O2:T ningún objeto, por ejemplo :N, que espera a O2:T se altera si recibe a O1:S.
Principio de sustitución de Liskov
T
:N
O1:S
m()
m()
S
:N
O2:T
m()
m()
S es subtipo de T
sii O2:T es sustituido por O1:S, y :N no cambia
Figura 4. 5 Ejemplo de aplicación del principio de sustitución de Liskov
114
Curso de OO dirigido por
la introducción de ambigüedad
4.8
La herencia, más aumento de la ambigüedad
La evolución segura del software
Pero lo interesante es ver el principio de sustitución desde otra perspectiva.
La herencia permite sustituir un objeto por otro, es decir cambiar una tarea
por otra, sin riesgo, siempre que las subclases sean subtipos de las
superclases.
Si tenemos una clase S que hereda de la clase T, para usar un objeto de la clase S
dondequiera que se espere un objeto de la clase T, y que el sistema siga funcionando
correctamente, la condición es que S debe ser subtipo de T. Si S no es subtipo de T no se
puede asegurar cuál será la consecuencia de la sustitución.
La relación de subtipo a tipo admite diseñar un tipo y después especializarlo sin
alterar los vínculos del sistema con el tipo. Se facilita la evolución del sistema. Por
ejemplo, primero diseñar un tipo A pensando en una tarea general y después, inventar un
subtipo B que particularice o especialice esa tarea. O al revés, diseñar un tipo y más
tarde convertirlo en un subtipo de otro tipo. La relación de subtipo a tipo facilita un
antes y un después sin daños colaterales. Figura 4. 6
P
Antes
A
v es A
a1
Op {
v ← O:A
v.m2
}
m2
Evolución
P
A
v es A
a1
Op {
v ← O:B
v.m2
}
m2
Después
Figura 4. 6 Evolución del software
115
B
m2
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
La Figura 4. 6 ilustra una línea de evolución de un sistema software.
Inicialmente, la clase P está asociada con la clase A (el atributo v es de la clase A) y sus
objetos utilizan el servicio m2, a través del mensaje v.m2, dirigido a objetos de A.
Después, se quiere que m2 haga otra tarea, relacionada con la anterior pero distinta y se
inventa la clase B que redefine m2. Los objetos de la clase P pueden utilizar este nuevo
servicio dirigiendo el mismo mensaje v.m2 a los objetos de B.
Si la clase B es subtipo de la clase A, entonces es segura la sustitución de los
objetos de la clase A por objetos de la clase B. El único reajuste que requiere la
operación cliente Op es cambiar la asignación “v ← O:A” por “v ← O:B”.
El comportamiento interno de un programa no cambia cuando se sustituye un
objeto de un tipo por un objeto del subtipo. Al programa, por ejemplo a los clientes
como P, le es indiferente uno u otro objeto, mantiene una relación ambigua con los
objetos de tipos y subtipos. Por tanto, se puede modificar la tarea, la función del
programa, sin alterar su trabajo. Una vez más se consolida la dirección de la
ambigüedad como línea de diseño software para conseguir acomodo a los cambios, para
conseguir plasticidad. La Figura 4. 7 ilustra la relación de ambigüedad (indiferencia) de
P hacia A.
116
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
B
a1
a2
a3
A
P
v es A
Op {
…
v ← O:?
v.m2
…
}
a1
a2
a3
m1
m2
m3
m1
m2
m3
E
a1
a2
a3
m1
m2
m3
Figura 4. 7 Relación de ambigüedad
En la figura AAA, la relación de P con el servicio m2 es ambigua porque se
establece a través del mensaje v.m2, que no le importa si va dirigido a objetos de A, de B
o de E. La clase P está ligada a la clase A, pero puede usar los servicios de todos los
subtipos de A, indiferentemente. Se podría decir que P sólo ve a A, los subtipos quedan
ocultos detrás del tipo. Los subtipos son los “detalles” que omite la abstracción A.
4.9
El aporte del principio de sustitución, la ambigüedad
El aporte del principio de sustitución es señalar el lado “bueno” de la herencia y
ofrecer una forma segura de usarlo. Pero, cuál es el lado “bueno” de la herencia porque
antes de estudiar el principio sólo se habían visto lados problemáticos. El propio título
del principio ofrece la respuesta: el lado bueno de la herencia es su capacidad de
producir elementos distintos, pero sustituibles. Es decir, la cualidad favorable de la
herencia es su capacidad para elevar la ambigüedad en el diseño y así facilitar el manejo
de la complejidad descriptiva y de incertidumbre.
La evolución, expansión o modificación del software a través de la herencia
debe cumplir las condiciones del principio de sustitución de Liskov para evitar
dificultades.
117
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
4.10 Condiciones del principio de sustitución
El cumplimiento del principio de sustitución exige que los métodos de las clases
derivadas deban mantener las siguientes relaciones con los métodos de la clase base:
1. La clase derivada debe tener un método correspondiente a cada método de la
clase base. Este método puede heredarse directamente de la clase base o
sobrescribirse.
2. Cada método de la clase derivada que se corresponda a un método de la clase
base debe requerir lo mismo o menos que la clase base. Es decir, si se
sobrescribe un método heredado de la clase base, las precondiciones del
método deben ser más débiles o permisivas que las del método de la clase
base. Dicho de otro modo, si se sobrescribe en una clase derivada un método
heredado de la clase base se debe garantizar que el nuevo método funcione
en las mismas condiciones y recibiendo los mismos argumentos que el
método heredado. El nuevo método no puede ser más restrictivo que el
método heredado.
3. Cada método de la clase derivada que se corresponda a un método de la clase
base debe garantizar lo mismo o más que la clase base. Es decir, si se
sobrescribe un método heredado de la clase base, las postcondiciones del
método de las clases derivada deben ser más fuertes o rigurosas que las
heredadas de la clase base. Dicho de otro modo, el método de la clase
derivada no debe comprometerse a ofrecer mayores resultados o resultados
diferentes; sólo debe comprometerse a hacer lo que hace el método de la
clase base, garantizando también las propiedades adicionales. Por ejemplo, si
un método de la clase base devuelve un número mayor que el argumento que
recibe, un método de una clase derivada podría devolver un número primo
mayor que el argumento. Pero no estaría permitido que el método de la clase
derivada devolviese un número menor o igual que el argumento.
4. Está permitido que la clase derivada introduzca nuevos métodos adicionales
que no aparezcan en la clase base.
118
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
Las clases derivadas deben garantizar también que se cumplan todas las
restricciones definidas sobre los atributos que hereda de la clase base. Por ejemplo, si en
la clase base se define un atributo de tipo entero y se establece una restricción para que
el atributo sea mayor o igual que cero, la clase derivada debe garantizar que se cumple
esta restricción y que el atributo en la clase derivada también tendrá siempre un valor
mayor o igual que cero.
Como podemos ver, cumplir con el principio de sustitución de Liskov es fácil,
siempre que no modifiquemos ni sobrescribamos los atributos y métodos que las clases
hijas heredan de sus madres y nos limitemos sólo a añadir nuevos atributos y métodos
adicionales en las clases hijas.
Si necesitamos modificar en las clases hijas los comportamientos heredados de
sus madres, entonces cumplir con el principio de Liskov puede resultar más
complicado. Puede incluso, que al tratar de cumplir con el principio, debamos
plantearnos si realmente la clase hija debe heredar de la clase madre.
4.11 Contraejemplo de la herencia. El cuadrado no es un
rectángulo
Un ejemplo clásico de uso peligroso de la herencia es la relación entre cuadrado
y rectángulo, analizada por Barbara Liskov en su trabajo sobre el principio.
Desde el colegio, todos sabemos que un rectángulo es una figura geométrica de
cuatro lados iguales dos a dos. Y que un cuadrado “es un” rectángulo con todos los
lados iguales.
La herencia se suele considerar como una relación “es un”, por tanto podría
parecernos natural que la clase Cuadrado heredase de la clase Rectángulo, ya que, al fin
y al cabo un cuadrado es un caso particular de rectángulo.
La clase Rectángulo básicamente podría tener un par de atributos, ancho y largo,
que son suficientes para definir el rectángulo. Nuestra clase tendría un método
establecerTamaño que recibe como argumentos el ancho y el largo del rectángulo que
queremos definir.
119
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
Rectangulo
-int ancho
-int largo
+establecerTamaño(int ancho, int largo)()
Figura 4. 8. Clase Rectángulo
Si la clase Cuadrado hereda de la clase Rectángulo, entonces Cuadrado tendrá
dos atributos, largo y ancho, y un método establecerTamaño que recibe como
argumentos el largo y el ancho del cuadrado. Ni los atributos ni los métodos heredados
de Rectángulo resultan muy útiles para la clase Cuadrado, ya que todos sabemos que en
un cuadrado el largo y el ancho son siempre iguales. Para poder utilizar los atributos y
el método heredado tendremos que añadir algunas restricciones:
1. el atributo largo tendrá siempre el mismo valor que el atributo ancho.
2. el método establecerTamaño tendrá como precondición que el valor del
argumento ancho sea igual al valor del argumento largo. En caso contrario
se lanzará una excepción o un mensaje de error.
120
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
Rectangulo
-int ancho
-int largo
+establecerTamaño(int ancho, int largo)()
Cuadrado
-int ancho
-int largo=ancho
+operación1()
//precondicion: ancho=largo
+establecerTamaño(int ancho, int largo)()
Figura 4. 9 Relación de herencia entre cuadrado y rectángulo
Preguntémonos ahora que ocurriría si sustituimos la clase Rectángulo por la
clase Cuadrado en algún lugar del código donde se use Rectángulo. ¿Podría un cliente
de Rectángulo trabajar con Cuadrado y seguir funcionando? La respuesta es no. A
menos que el cliente conozca y cumpla las restricciones de Cuadrado, si tratamos de
sustituir Cuadrado por Rectángulo el resultado será impredecible.
¿Qué es lo que está ocurriendo en términos del principio de sustitución de
Liskov? El principio no se cumple porque los atributos y métodos de Cuadrado son más
restrictivos que los de Rectángulo. Cuadrado no es subtipo de Rectángulo. Por tanto, si
no queremos tener resultados inesperados no deberíamos utilizar la herencia de
Cuadrado a Rectángulo. Herencia que, por otra parte, no aporta ninguna ventaja al
diseño, ya que Cuadrado necesita sobrescribir todo lo que hereda de Rectángulo para
funcionar.
Nuestra intuición nos ha llevado a pensar que Cuadrado debería heredar de
Rectángulo porque conceptualmente un cuadrado es un rectángulo. Sin embargo, hemos
visto que esta clasificación, a pesar de ser cierta, no aporta ninguna ventaja a nuestro
diseño y sí puede traernos serios inconvenientes. La herencia puede ser contradictoria
con la clasificación usual. En los diseños orientados a objetos, copiar la realidad o
121
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
dejarse arrastrar por ella, no siempre es una buena idea. Un cuadrado es un rectángulo
en geometría, pero la clase Cuadrado no es un subtipo de la clase Rectángulo en el
software.
4.12 Las clases abstractas
El ejemplo del cuadrado y el rectángulo demuestra que la relación geométrica de
tipo y subtipo entre rectángulo y cuadrado no es aplicable a una relación software entre
la clase Rectángulo y la clase Cuadrado. El universo software y el universo ajeno al
software son universos distintos.
En el universo software, si se quiere conseguir el intercambio seguro de objetos
cuadrados y objetos rectángulos, hay que aprovechar la relación tipo – subtipo, pero de
otra forma: definiendo a las clases Cuadrado y Rectángulo como subtipos (hermanos)
de un tipo que se podría denominar Figura. En la Figura 4. 10 se muestra esta relación,
ampliada con la clase Paralelogramo (el “supertipo” geométrico de rectángulo y
cuadrado, aquí obligado a ser un subtipo más).
Figura
color
pintar
ampliar
mover
área
Paralelogramo
Cuadrado
P
largo
pintar
ampliar
mover
área
p
largo
ancho
p
largo
ancho
ángulo
pintar
ampliar
mover
área
pintar
ampliar
mover
área
Rectángulo
Figura 4. 10 Una relación prudente entre las clases Cuadrado, Rectángulo y
Paralelogramo
La relación de la Figura 4. 10 permite el uso intercambiable de objetos cuadrado,
rectángulo y paralelogramo porque todos son subtipos de un tipo. La dificultad de la
122
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
relación es la definición del código de los métodos del tipo Figura. Pero el enfoque de
objetos salva esta dificultad, dando otro paso más en su capacidad de expresar
ambigüedad: permite que los métodos del tipo sean sólo nombres, nada de código
interior. Es decir, que sean una abstracción de los métodos homónimos de los subtipos.
Este hecho da lugar a las siguientes definiciones:
Se denomina método abstracto al método que sólo expresa la cabecera y carece
de código interno. Dicho de otro modo, un método que está declarado pero no
implementado [Booch 94]. Por ejemplo, los métodos pintar, mover, ampliar y
área, de la clase Figura. También se les llama métodos virtuales o diferidos. En
UML los métodos abstractos se escriben con letra cursiva. En el presente texto,
la letra cursiva tiene el objetivo de resaltar, no indica abstracción.
Se denomina clase abstracta a la clase que contiene al menos un método
abstracto porque refleja la abstracción de ese método. Por ejemplo, la clase
Figura. También se les llama clases virtuales o diferidas. Mientras que una
clase es la definición de un conjunto de objetos, una clase abstracta es la
definición de un conjunto de clases. Es una abstracción de abstracciones que
eleva la capacidad de expresar ambigüedad del enfoque estructurado. La
carencia de código, en al menos un método, impide que existan objetos de una
clase abstracta. En UML las clases abstractas se escriben con letra cursiva.
En UML se denomina interfaz a una colección de operaciones que tiene un
nombre y que se usa para especificar un servicio de una clase. Es un paso más
elevado en el camino de la ambigüedad en forma de abstracción. Una interfaz
carece de atributos. UML realza la importancia de este tipo de abstracción al
darle un nombre propio, pero la idea es anterior y coincide con la idea de clase
abstracta pura que utilizan otros autores. El problema de usar el nombre de
interfaz es que aumenta la polisemia de esta palabra. Frecuentemente se utiliza
la palabra inglesa “interface” como si fuese española. Pero la traducción de la
palabra inglesa es interfaz, puesto que “face” significa cara, faz. Por cierto,
como la palabra faz es femenina, la palabra interfaz también es femenina y debe
ser precedida por el artículo “la”.
123
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
La clase Figura contiene solo el atributo color; prescinde de otros atributos que
pueden ser comprometedores (restrictivos), por ejemplo, p (posición) y largo. Si se
coloca el atributo largo, la clase Figura se compromete sólo con figuras geométricas
que tengan algo recto, para que se pueda definir largo. El atributo p (posición) parece
más universal porque cualquier figura geométrica en la pantalla debe tener una posición.
Es cierto, pero el punto de la figura que determina la posición puede tomar distintos
nombres, por ejemplo “vértice superior izquierdo” en los paralelogramos y similares, y
“centro” en los círculos. Para facilitar que la clase Figura sea el tipo de todas las clases
de figuras conviene prescindir de atributos restrictivos, conviene elevar su capacidad de
expresar ambigüedad.
4.13 Las clases generales, una solución alternativa, pero…
Una solución alternativa al problema de los cuadrados y rectángulos es
aprovechar la clase Rectángulo para que también opere con cuadrados, pero como
rectángulos, sin distinguirlos. La clase Rectángulo no diferencia entre cuadrados y
rectángulos, pero los humanos podrían hacerlo. Con una sola clase se ha resuelto el
problema de los cuadrados y los rectángulos.
La solución anterior se puede extender a la clase Paralelogramo y ahorrarse la
clase Rectángulo. Ahora, una sola clase sirve para resolver tres problemas: el cuadrado,
el rectángulo y el paralelogramo, que son casos particulares unos de otros desde el
punto de vista de la geometría. Figura 4. 11
124
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
ángulo
ancho
largo
P
Cuadrado
Rectángulo
Paralelogramo
Figura*
Figura 4. 11 Generalización de una clase
Un paso más allá conduce a la clase polígono irregular (por qué limitarse al
regular) que se consigue con reajustes de atributos y métodos (cada vez más generales).
Y continuando con la tentación (para qué tantas clases), se podría disponer de una única
clase Figura* (para distinguirla de la otra) capaz de operar con cualquier figura
geométrica. La Figura 4. 11 ilustra un posible proceso de generalización que conduce a
la clase Figura*. Se ha cambiado la disposición usual para realzar el aumento de los
atributos a medida que la clase aumenta en generalidad. La confusión que produce la
figura es un efecto colateral, que también se produce cuando se trabaja con clases
generales.
Pero, ¿para qué todo en una sola clase? Una sola clase no es necesariamente más
simple que varias clases. La complejidad no se reduce por empaquetarla en un solo
elemento. Generalmente sucede al revés. La complejidad de la clase Figura* es mayor
que la complejidad total de las clases separadas y además es más embarazosa de
manejar.
Por ejemplo, para que la clase Rectángulo opere con cuadrados hay que darle un
valor al atributo ancho imprescindible para la clase, pero innecesario para el humano.
La clase Paralelogramo exigiría además, el ángulo.
Otra variante sería definir los atributos de Rectángulo en términos de los vértices
de la diagonal y el método pintar, de la clase Rectángulo, pintaría la figura inscrita en
125
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
esos vértices, sin distinguir cuadrado de rectángulo. La responsabilidad de distinguir se
traspasa a la persona, cliente de la clase Rectángulo, que debe calcular las coordenadas
del otro vértice, tanto para el rectángulo como para el cuadrado. Si se equivoca en la
operación de cálculo la figura sale mal. La situación es peor si la clase Paralelogramo
se utiliza también para cuadrados y rectángulos. No obstante, puede ser útil disponer de
una clase donde la figura se describa a través de vértices o puntos.
Comúnmente, mientras mayor sea la generalidad de la clase que realiza la tarea,
más se complica su desarrollo, mantenimiento, manipulación, en fin todo. El desarrollo
progresivo de una clase como Figura* significa modificar una y otra vez el interior de
la clase y a quienes usan la clase. El desarrollo de una vez obliga a enfrentarse a un
problema mayor que el problema de enfrentarse cada vez con una figura simple. El
mantenimiento para corregir y perfeccionar una clase como Figura* aborda un
mecanismo más complejo montado en una sola pieza donde todo está relacionado con
todo. De la manipulación ya se ha hablado. Precisa información redundante cuando
opera con casos particulares más simples, muchas veces exige trabajo extra de quienes
usan la clase y por estas dos causas es más susceptible a equivocaciones.
La clase Figura* es una clase concreta que implementa una herramienta
general. Es como una piedra que sirve para clavar, cortar, lanzar, dibujar,
calzar,…según se use. Hay que soportar un peso innecesario cuando se usa para cortar y
hay que tener cuidado de no cortarse, al usar la piedra para clavar. Además, cuando se
trate de mejorar alguno de sus usos, se puede perjudicar otro porque todos ellos están
muy ligados, en la misma piedra. Los humanos fueron especializando los distintos usos
de la piedra en instrumentos particulares. También conviene hacerlo en el software.
4.14 Las clases particulares, beneficios y problema
Los instrumentos particulares sólo exigen la información específica que
necesitan; facilitan el desarrollo porque se pueden construir y probar uno a uno, o al
unísono distribuyendo el trabajo de desarrollo de los distintos instrumentos; se puede
modificar cualquiera de ellos sin afectar a los restantes. Incluso, se puede llegar a
construir un instrumento muy general, como Figura*, para resolver situaciones muy
generales, pero como un instrumento más de uso específico en esas situaciones. Sin
126
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
embargo, a veces todas estas ventajas quedan opacadas por el problema de la
diversidad. Sucede con frecuencia en el software.
4.15 La ambigüedad, solución al problema de la diversidad
El enfoque de objetos facilita la resolución del problema de la diversidad a
través de sus recursos para expresar y manejar ambigüedad. La clase Figura es una
abstracción que oculta u omite la diversidad (las clases) pero que viabiliza el acceso a
esa diversidad. Figura 4. 12
cliente
cliente
Figura
Figura*
,y3,…
X1,y1,x2,y2,x3
Herramienta general
Herramientas especializadas
Figura 4. 12 Contraste entre soluciones
La Figura 4. 12 contrasta del diseño de Figura y sus subtipos con el diseño de la
clase Figura*. Ambos diseños muestran una cara uniforme a sus elementos clientes. El
primero por ser una abstracción y el segundo por ser una implementación de una
herramienta general. La cara de la abstracción es simple porque no se compromete con
los detalles, muestra sólo la esencia. La cara de Figura* es compleja porque tiene que
expresar toda la información que necesita para resolver un problema complejo: ocuparse
de (casi) cualquier figura. La clase Figura es el contexto que permite dirigir la tarea al
objeto de clase encargada de una figura específica. El objeto de la clase Figura* es el
encargado de realizar la tarea, cualquiera que sea la figura. La clase Figura es una
abstracción, mientras que la clase Figura* es un elemento concreto, particular, aunque
implemente un mecanismo general.
127
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
4.16 La ambigüedad es la clave, no la división
El contraste de ambos diseños puede sugerir que los beneficios de Figura y sus
subtipos deriva de aplicar el principio de divide y vencerás o de su versión software:
modularizar. Pero no es así, ni tampoco está asociado con cohesión y acoplamiento. La
simplificación se obtiene mediante la especificidad y la introducción de ambigüedad.
La clase Figura* es un módulo cohesivo con bajo acoplamiento. Es un módulo
bien diseñado. Su división en partes más pequeñas no daría lugar a los subtipos porque
la división de un mecanismo general en trozos no produce mecanismos particulares.
Incluso, la propia clase Figura* podría ser un subtipo más de Figura. Coexistiría el
supuesto todo y las partes al mismo tiempo.
La fuente de los subtipos es la especificidad, lo individual y particular de cada
figura, que existe posiblemente antes de lo general de cualquier figura. El cuadrado, el
círculo y el triángulo existieron como concepto antes que el concepto general de figura.
La fuente de la clase Figura es la abstracción, la expresión de lo común a todas
las figuras del sistema software. La introducción de este elemento ambiguo es la
decisión de diseño que permite homogeneizar la diversidad, disponer de un acceso
común a todas las clases particulares.
La ambigüedad que expresa la abstracción simplifica la complejidad descriptiva
porque reduce la cantidad de elementos que se necesitan para describir el diseño. La
existencia de la clase Figura describe que el sistema se ocupa de figuras. Sin esta clase
habría que decir que el sistema se ocupa de cuadrados, rectángulos, … Además, la
ambigüedad simplifica también la complejidad de incertidumbre porque simplifica el
trabajo de reajuste presente y futuro del sistema.
4.17 La herencia múltiple
Hasta ahora se ha estudiado la herencia simple de un solo progenitor
(partenogenética), pero también existe la herencia de varios progenitores.
128
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
Se denomina herencia múltiple a la herencia donde una subclase tiene más de
una superclase directa. La Figura 4. 13 ilustra la herencia múltiple.
A
B
a
b
ma
mb
C
a
b
ma
mb
Figura 4. 13 Herencia múltiple
Haciendo un guiño a la biología, la herencia es como una reproducción sexuada
donde cada progenitor aporta sus cualidades a los hijos. En el caso del modelo orientado
a objetos los hijos heredan el contenido completo sus progenitores. Por ejemplo,
refiriéndonos a la Figura 4. 13, la clase C hereda todos los atributos y métodos de la
clase A y, también, todos los atributos y métodos de la clase B.
Algunos textos utilizan este mecanismo para obtener elementos software
híbridos. Otros, como recurso de clasificación mixta. Pero, si la herencia simple es
peligrosa, la herencia múltiple es múltiplemente peligrosa. Por ejemplo, a menudo se
ilustra la herencia múltiple a través de las superclases Barco y Automóvil para obtener la
clase Anfibio. Un problema es la distorsión del sujeto que se pretende representar
porque el anfibio es barco cuando está en el agua y automóvil cuando está en tierra,
mientras que la clase Anfibio es Barco y Automóvil a la vez porque la herencia es un
mecanismo estático (en tiempo de compilación).
Si el mecanismo hereditario fuese dinámico, en tiempo de ejecución, un objeto
pudiera ser de una clase ahora y de otra después. No habría confusiones porque no sería
de dos clases en el mismo momento. Aunque los lenguajes comerciales de
129
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
programación orientada a objetos todavía no incorporan una herencia dinámica, es
posible conseguir efectos semejantes con algunas técnicas, referidas por [Fowler 97].
Otro problema es solapamiento del contenido “genético” heredado. Por ejemplo,
la Figura 4. 14 muestra el resultado de la herencia múltiple de las clases Gallina y
Tiburón para conseguir una clase con propiedades de correr y nadar.
Gallina
Tiburón
boca
boca
correr
nadar
Gallirón
boca
boca
correr
nadar
Figura 4. 14 Herencia múltiple con problema.
La dificultad de la clase Gallirón está en el atributo boca porque tiene dos
especificaciones distintas. Una manera de resolver el solapamiento genético es con la
herencia dinámica. Otra, sería poder “marcar” la propiedad que se desea heredar, pero
no está permitido en los lenguajes comerciales actuales. Una tercera manera de evitar el
solapamiento es evitar que las superclases aporten elementos en posible conflicto.
El lenguaje Java adopta esta última solución de forma radical. Sólo permite la
herencia múltiple de las denominadas interfaces (clases abstractas puras que contienen
porque suprime los atributos y reduce las operaciones a simples declaraciones. La
implementación de las operaciones, en clase que hereda, resuelve las colisiones
potenciales. Esta modalidad de la herencia múltiple es una forma segura y útil para
dotar a una clase con más de una cara (interfaz).
130
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
4.18 Aproximación al patrón adaptador
La doble cara de una clase facilita adaptar sus operaciones a la cara que
necesitan otras clases para usar esas operaciones. El denominado patrón adaptador
aprovecha el efecto de la doble cara que otorga la herencia múltiple. Figura 4. 15
A
B
m
n
i
C
M
G
i
n
m
i
i
i{
invocar n
}
Diseño para conseguir que n sea otro i
Figura 4. 15 Variante del patrón adaptador
En la Figura 4. 15, la clase A contiene un método n que se quiere incluir como
una implementación más del método i, declarado en la clase abstracta pura B. Entonces,
se diseña una clase C que herede de las clases A y B el método n y la declaración del
método i respectivamente. Como ambos elementos se han juntado en la clase C, basta
con definir que la implementación del método i consiste en invocar al método n para
conseguir que n sea una i más. Esta última técnica se denomina envolver.
4.19 La herencia vista desde el código, un ejemplo
En capítulos anteriores se diseñaron pequeños sistemas para trabajar con
triángulos, círculos y rectángulos. Ahora se quiere integrar estos sistemas en uno solo.
A continuación se muestran los diseños de cada clase.
131
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
Circulo
-int centrox
-int centroy
-int radio
-Color color
-boolean haydatos
+Circulo()
+paint(entrada Graphics g)
+mover(entrada int desplazamientox, entrada int desplazamientoy)
+ampliar(entrada int zoomout)
+reducir(entrada int zoomin)
+borrar()
FormularioCirculo
Figura 4. 16 Clase Círculo
Triangulo
-int verticesuperiorx
-int verticesuperiory
-int base
-int altura
-Color color
-boolean haydatos
+Triangulo()
+paint(entrada Graphics g)
+mover(entrada int desplazamientox, entrada int desplazamientoy)
+ampliar(entrada int zoomout)
+reducir(entrada int zoomin)
+borrar()
FormularioTriangulo
Figura 4. 17 Clase Triángulo
Rectangulo
-int origenx
-int origeny
-int base
-int altura
-Color color
-boolean haydatos
+Rectangulo()
+paint(entrada Graphics g)
+mover(entrada int desplazamientox, entrada int desplazamientoy)
+ampliar(entrada int zoomout)
+reducir(entrada int zoomin)
+borrar()
Figura 4. 18 Clase Rectángulo
132
FormularioRectangulo
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
Las clases Círculo, Rectángulo y Triángulo son distintas entre sí porque tienen
atributos distintos y comportamientos distintos: un círculo se pinta de forma distinta que
un rectángulo y que un triángulo; también la forma de ampliarlo, reducirlo o moverlo es
distinta a la del rectángulo y el triángulo. Pero todas ellas son figuras, por tanto se podrá
diseñar una abstracción que exprese la esencia de interés y omita los detalles que las
diferencian. La clase Figura, discutida antes, puede ser un punto de partida para este
diseño particular.
Las cabeceras de las operaciones de cada figura coinciden porque han sido
diseñadas con una disciplina, manteniendo un estilo. Si las cabeceras no coincidieran
sería posible aplicar una técnica de adaptación para uniformarlas. La coincidencia es
necesaria para conseguir que las operaciones sean polimórficas cuando se relacionen
con las operaciones homónimas de la clase Figura. Figura 4. 19
Figura
-Color color
-boolean haydatos
+Figura()
+paint(entrada Graphics g)
+mover(entrada int desplazamientox, entrada int desplazamientoy)
+ampliar(entrada int zoomout)
+reducir(entrada int zoomin)
+borrar()
+borrar()
Figura 4. 19 Clase abstracta Figura
En la nueva clase Figura se ha incluido el método concreto borrar() porque, de
inicio, se considera que será común a todos los subtipos, independientemente de la
figura. Una figura se borra pidiéndole que se pinte con el color de fondo. La
implementación de borrar() estará en la clase madre y las hijas heredarán el código de
ella. La presencia de este método hace que la clase Figura sea abstracta, pero no
abstracta pura o interfaz. Además, en esta clase se han añadido los atributos color y
haydatos que son comunes a todas las figuras.
En el diagrama el nombre de la clase Figura está escrito en letra cursiva porque
UML usa este tipo de letra para indicar que un elemento es abstracto. También está
133
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
escritos con la misma letra los métodos abstractos o virtuales paint(), mover(), ampliar()
y reducir().
La Figura 4. 20 muestra el diseño del diagrama de clases del sistema.
Figura 4. 20 Diagrama de clases de Figura polimórfica
Entre la clase Ventana y la clase Figura existe una relación de agregación
porque la clase Ventana contiene un atributo Figura de tipo Figura. Este atributo será
en un momento dado un objeto Círculo, Triángulo o Rectángulo. Pero como todos
comparten la misma especificación, la clase Ventana puede tratarlos a todos por igual
utilizando la especificación de Figura, sin necesidad de conocer el tipo de figura,
excepto en el instante de la creación de los objetos.
Un constructor está ceñido a crear objetos de una clase específica, no puede
crear objetos de otras clases por su especificidad. La creación de un objeto no es una
operación polimórfica. De manera que el objeto creador establece una fuerte relación
unívoca hacia el objeto creado. Para debilitar esta relación se utilizan diversas técnicas
según sea el caso. Algunas de ellas serán vistas en el curso.
La clase Ventana no puede crear objetos de tipo Figura porque Figura es una
clase abstracta. La clase Ventana creará objetos de tipo Círculo, Rectángulo y Triángulo
según elija, en la pantalla, el usuario del sistema. Por eso hay una relación de
134
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
dependencia de Ventana a cada una de las clases Círculo, Triángulo y Rectángulo. Una
vez que el usuario haya elegido la figura se asignará al atributo figura el objeto creado y
Ventana podrá trabajar con él a través de la especificación de Figura. Se abre la puerta
al uso del polimorfismo.
Volviendo al principio de sustitución de Liskov, observamos que en este caso
Círculo, Triángulo y Rectángulo son subtipos de Figura ya que podemos sustituir
Figura por cualquiera de ellos en el código de Ventana y el resultado será correcto.
135
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
4.20 El polimorfismo en Java
Veamos cómo se implementa el polimorfismo en el lenguaje Java. A
continuación mostramos el código de las clases Ventana, Figura, Círculo, Triángulo y
Rectángulo del ejemplo anterior. El código del programa completo puede verse en el
anexo.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Ventana extends JFrame implements ActionListener{
Declaramos en ventana el
atributo figura de la clase
Figura
private Figura figura;
private JPanel paneloperaciones, panelfiguras;
private JButton circulo, rectangulo, triangulo, ampliar, reducir, arriba, abajo, izqda, dcha;
public Ventana() {
//Pintar la ventana vacía
setTitle("Pintar Figuras");
asignarLookAndFeel();
setCloseClick();
setExtendedState(MAXIMIZED_BOTH);
configurarGUI();
//Repintar la ventana con la figura
pack();
setExtendedState(MAXIMIZED_BOTH);
setVisible(true);
}
private void asignarLookAndFeel()
{
//Forzar el Look and Feel de la ventana al del sistema
String laf = UIManager.getSystemLookAndFeelClassName();
try {
UIManager.setLookAndFeel(laf);
}
catch (UnsupportedLookAndFeelException exc)
{System.err.println("Unsupported: " + laf);}
catch (Exception exc)
{System.err.println("Error cargando: " + laf);}
}
private void setCloseClick()
{
//Controlar el cierre de la ventana
addWindowListener(new WindowAdapter()
{ public void windowClosing(WindowEvent e)
{System.exit(0);}
});
}
private void configurarGUI(){
//Crear los paneles de botones de figuras y operaciones
136
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
panelfiguras = new JPanel();
panelfiguras.setLayout(new GridLayout());
paneloperaciones = new JPanel();
paneloperaciones.setLayout(new GridLayout());
//Crear los botones de figuras y añadirlos al panel de figuras
circulo=new JButton("Pintar Circulo");
circulo.addActionListener(this);
panelfiguras.add(circulo);
rectangulo=new JButton("Pintar Rectangulo");
rectangulo.addActionListener(this);
panelfiguras.add(rectangulo);
triangulo=new JButton("Pintar Triangulo");
triangulo.addActionListener(this);
panelfiguras.add(triangulo);
//Crear los botones de operaciones y añadirlos al panel de operaciones
//Tienen que estar inhabilitados hasta que se haya elegido una figura
ampliar=new JButton("Ampliar");
ampliar.addActionListener(this);
ampliar.setEnabled(false);
paneloperaciones.add(ampliar);
reducir=new JButton("Reducir");
reducir.addActionListener(this);
reducir.setEnabled(false);
paneloperaciones.add(reducir);
arriba=new JButton("Mover arriba");
arriba.addActionListener(this);
arriba.setEnabled(false);
paneloperaciones.add(arriba);
abajo=new JButton("Mover abajo");
abajo.addActionListener(this);
abajo.setEnabled(false);
paneloperaciones.add(abajo);
izqda=new JButton("Mover izqda");
izqda.addActionListener(this);
izqda.setEnabled(false);
paneloperaciones.add(izqda);
dcha=new JButton("Mover dcha");
dcha.addActionListener(this);
dcha.setEnabled(false);
paneloperaciones.add(dcha);
//Añadir los paneles de botones: figuras en la parte superior y
//operaciones en la parte inferior de la ventana
getContentPane().add(panelfiguras,BorderLayout.NORTH);
getContentPane().add(paneloperaciones,BorderLayout.SOUTH);
}
/** Manejador de eventos para controlar los botones */
public void actionPerformed(ActionEvent e)
{
int zoom=2;
int desplazamiento=20;
Object boton=e.getSource();
if (boton == circulo){
figura = new Circulo();
ampliar.setEnabled(true);
reducir.setEnabled(true);
arriba.setEnabled(true);
abajo.setEnabled(true);
izqda.setEnabled(true);
dcha.setEnabled(true);
137
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
}
if (boton == rectangulo){
figura = new Rectangulo();
ampliar.setEnabled(true);
reducir.setEnabled(true);
arriba.setEnabled(true);
abajo.setEnabled(true);
izqda.setEnabled(true);
dcha.setEnabled(true);
}
if (boton == triangulo){
figura = new Triangulo();
ampliar.setEnabled(true);
reducir.setEnabled(true);
arriba.setEnabled(true);
abajo.setEnabled(true);
izqda.setEnabled(true);
dcha.setEnabled(true);
}
if (boton == reducir)
figura.reducir(zoom);
if (boton == ampliar)
figura.ampliar(zoom);
if (boton == arriba)
figura.mover(0,-desplazamiento);
if (boton == abajo)
figura.mover(0,desplazamiento);
if (boton == izqda)
figura.mover(-desplazamiento,0);
if (boton == dcha)
figura.mover(desplazamiento,0);
this.repaint();
}
public void paint (Graphics g)
{
super.paint(g);
if (figura!=null)
figura.paint(g);
}
public static void main(String[] args) {
new Ventana();
}
}
Declaramos
la
abstracta Figura
public abstract class Figura extends JPanel{
clase
protected Color color;
protected boolean haydatos=false;
public Figura() {
El método paint debería ser
abstracto, pero para poder utilizar
el API swing necesitamos que
esté implementado en todas las
clases.
}
public void paint(Graphics g){
super.paint(g);
138
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
}
public abstract void mover (int desplazamientox, int desplazamientoy);
public abstract void ampliar (int zoomin);
Declaramos los métodos
abstractos mover, ampliar y
reducir
public abstract void reducir (int zoomout);
public void borrar(){
//Pintarme del color del fondo de la ventana
color= this.getBackground();
repaint();
}
}
Declaramos
Círculo que
Figura
public class Circulo extends Figura{
//Coordenada x del centro
private int centrox;
la
clase
hereda de
//Coordenada y del centro
private int centroy;
//Radio
private int radio;
//Crea una nueva instancia de Circulo
public Circulo() {
// Mostrar el formulario para obtener los datos del circulo
FormularioCirculo formulario= new FormularioCirculo();
//JDialog dialog = new JDialog(this, "Introduzca los datos del circulo", true);
JDialog dialog =new JDialog();
dialog.setTitle("Introduzca los datos del circulo");
dialog.setModal(true);
dialog.setContentPane(formulario);
dialog.setDefaultCloseOperation(javax.swing.WindowConstants.HIDE_ON_CLOSE);
dialog.pack();
dialog.show();
// Obtener los datos introducidos por el usuario
centrox=formulario.obtenerCentrox();
centroy=formulario.obtenerCentroy();
radio=formulario.obtenerRadio();
color=formulario.obtenerColor();
haydatos=true;
}
public void paint (Graphics g)
{
super.paint(g);
//Si el usuario ha introducido los datos pinta el circulo
if (haydatos){
g.setColor(color);
g.drawOval(centrox-radio, centroy-radio,2*radio,2*radio);
g.fillOval(centrox-radio, centroy-radio,2*radio,2*radio);
g.dispose();
}
}
139
Implementamos el
método paint
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
public void mover (int desplazamientox, int desplazamientoy){
Implementamos el
método mover
centrox=centrox+desplazamientox;
centroy= centroy+desplazamientoy;
}
}
public void ampliar (int zoomin){
Implementamos el
método ampliar
if (zoomin > 0){
radio=radio*zoomin;
}
}
Implementamos el
método reducir
public void reducir (int zoomout){
if (zoomout > 0){
radio=radio/zoomout;
}
}
Declaramos
la
clase
Rectángulo que hereda de
Figura
public class Rectangulo extends Figura{
//Coordenada x del vertice superior izquierdo
private int origenx;
//Coordenada y del vertice superior izquierdo
private int origeny;
//Base
private int base;
//Altura
private int altura;
//Crea una nueva instancia de Rectangulo
public Rectangulo() {
// Mostrar el formulario para obtener los datos del rectangulo
FormularioRectangulo formulario= new FormularioRectangulo();
JDialog dialog =new JDialog();
dialog.setTitle("Introduzca los datos del rectangulo");
dialog.setModal(true);
dialog.setContentPane(formulario);
dialog.setDefaultCloseOperation(javax.swing.WindowConstants.HIDE_ON_CLOSE);
dialog.pack();
dialog.show();
// Obtener los datos introducidos por el usuario
origenx=formulario.obtenerOrigenx();
origeny=formulario.obtenerOrigeny();
base=formulario.obtenerBase();
altura=formulario.obtenerAltura();
color=formulario.obtenerColor();
haydatos=true;
}
Implementamos el
método paint
public void paint(Graphics g) {
super.paint(g);
//Si el usuario ha introducido los datos pinta el rectangulo
if (haydatos){
g.setColor(color);
140
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
g.drawRect(origenx,origeny,base,altura);
g.fillRect(origenx,origeny,base,altura);
g.dispose();
}
}
public void mover (int desplazamientox, int desplazamientoy){
origenx=origenx+desplazamientox;
origeny=origeny+desplazamientoy;
Implementamos el
método mover
}
public void ampliar (int zoomin){
Implementamos el
método ampliar
if (zoomin > 0){
base= base * zoomin;
altura=altura*zoomin;
}
}
public void reducir (int zoomout){
Implementamos el
método reducir
if (zoomout > 0){
base= base / zoomout;
altura=altura / zoomout;
}
}
}
Declaramos
la
clase
Triángulo que hereda de
Figura
public class Triangulo extends Figura{
//Coordenada x del vertice superior
private int verticesuperiorx;
//Coordenada y del vertice superior
private int verticesuperiory;
//Base
private int base;
//Altura
private int altura;
// Crea una nueva instancia de Triangulo
public Triangulo () {
// Mostrar el formulario para obtener los datos del triangulo
FormularioTriangulo formulario= new FormularioTriangulo();
JDialog dialog =new JDialog();
dialog.setTitle("Introduzca los datos del triangulo");
dialog.setModal(true);
dialog.setContentPane(formulario);
dialog.setDefaultCloseOperation(javax.swing.WindowConstants.HIDE_ON_CLOSE);
dialog.pack();
dialog.show();
// Obtener los datos introducidos por el usuario
verticesuperiorx=formulario.obtenerVerticeSuperiorx();
verticesuperiory=formulario.obtenerVerticeSuperiory();
base=formulario.obtenerBase();
altura=formulario.obtenerAltura();
color=formulario.obtenerColor();
141
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
haydatos=true;
}
Implementamos el
método paint
public void paint(Graphics g) {
int [] coordenadasx=getCoordenadasX();
int [] coordenadasy=getCoordenadasY();
super.paint(g);
//Si el usuario ha introducido los datos pinta el triangulo
if (haydatos){
g.setColor(color);
g.drawPolygon(coordenadasx, coordenadasy, 3);
g.fillPolygon(coordenadasx, coordenadasy, 3);
g.dispose();
}
}
private int [] getCoordenadasX(){
int [] coordenadasx = new int [3];
coordenadasx[0]=verticesuperiorx;
coordenadasx[1]=verticesuperiorx-(base/2);
coordenadasx[2]=verticesuperiorx+(base/2);
return coordenadasx;
}
private int [] getCoordenadasY(){
int [] coordenadasy= new int[3];
coordenadasy[0]=verticesuperiory;
coordenadasy[1]=verticesuperiory+altura;
coordenadasy[2]=verticesuperiory+altura;
return coordenadasy;
}
public void mover (int desplazamientox, int desplazamientoy){
Implementamos el
método mover
verticesuperiorx = verticesuperiorx + desplazamientox;
verticesuperiory = verticesuperiory + desplazamientoy;
}
public void ampliar (int zoomin){
Implementamos el
método ampliar
if (zoomin > 0){
base= base * zoomin;
altura=altura*zoomin;
}
}
Implementamos el
método reducir
public void reducir (int zoomout){
if (zoomout > 0){
base = base / zoomout;
altura = altura / zoomout;
}
}
}
142
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
4.21 Ejercicio
Reutilice el la solución de figuras geométricas para dibujar una cara. El aspecto
de la cara se muestra a continuación. Puede suponer que el contorno de la cara es un
círculo, que los ojos también son círculos y que la boca es un rectángulo muy estrecho.
143
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
Una solución
Pensar en objetos es asociar a un objeto la tarea de dibujar la cara. Sería un
objeto compuesto de otros objetos; uno por cada componente de la cara. Por ejemplo:
 contorno, es un objeto de la clase Círculo asociado con el contorno de la cara.
 ojoderecho y ojoizquierdo, son dos objetos de la clase Círculo asociados con los
ojos.
 boca, es un objeto de la clase Rectángulo asociado con la boca.
Además conviene considerar como atributos del objeto Cara
 centrox y centroy, son dos números enteros para indicar las coordenadas del
centro de la cara. Servirán para situar la cara en la pantalla.
 tamanyo, un número entero para indicar el tamaño de la cara.
 color, un atributo de tipo Color (tipo predefinido en Java) que indica el color de
la cara.
La cara estará contenida en una ventana, igual que en las figuras geométricas,
porque se está trabajando en un entorno de ventanas (windows). La Figura 4. 21 muestra
el diagrama de clases.
144
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
Cara
Ventana
-Cara cara
+Ventana()
-asignarLookAndFeel()
-configurarGUI()
-setCloseClick()
+paint(entrada Graphics g)
+main(entrada String[] args)
-Circulo contorno
-Circulo ojoderecho
-Circulo ojoizquierdo
-Rectangulo boca
-int centrox
-int centroy
-int tamanyo
-Color color
+Cara()
+paint(entrada Graphics g)
Figura
Circulo
Rectangulo
Triangulo
Figura 4. 21 Diagrama de clases de Pintar Cara
Las relaciones de agregación entre las clases Cara, Círculo y Rectángulo
expresan que los objetos de la clase Cara están compuestos por objetos de las clases
Círculo y Rectángulo. Estas relaciones reflejan el tipo de los atributos contorno,
ojoderecho, ojoizquierdo y boca.
Veamos a continuación el código de la clase Cara escrito en el lenguaje Java
public class Cara extends JPanel{ //extendsJPanel es necesario para usar el APi swing
private Circulo ojoderecho, ojoizquierdo; //ojos
private Rectangulo boca; //boca
private Circulo contorno; //contorno
private int tamanyo=200; //tamaño de la cara
private int centrox=500; //coordenada x del centro de la cara
private int centroy=350; //coordenada y del centro de la cara
private Color color=Color.YELLOW; //color de fondo de la cara
// Crea una nueva instancia de Cara
public Cara() {
//crear el contorno
contorno = new Circulo(centrox, centroy, tamanyo, color);
//crear los ojos
ojoizquierdo = new Circulo(centrox-(tamanyo/3), centroy-(tamanyo/4), 10, Color.BLACK );
ojoderecho = new Circulo(centrox+(tamanyo/3), centroy-(tamanyo/4), 10, Color.BLACK );
//crear la boca
boca = new Rectangulo(centrox-(tamanyo/4), centroy+(tamanyo/2), tamanyo/2, 2, Color.BLACK);
}
145
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
public void paint (Graphics g)
{
super.paint(g); //necesario para utilizar el API swing
//pintar el contorno
contorno.paint(g);
//pintar los ojos
ojoderecho.paint(g);
ojoizquierdo.paint(g);
//pintar la boca
boca.paint(g);
}
}
La solución pinta la cara reutilizando la estructura de figuras desarrollada
anteriormente. La Figura 4. 22 muestra el resultado de la ejecución del programa.
Figura 4. 22 Resultado de Pintar Cara
146
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
4.22 Otro ejercicio
La cara que pintamos no les gustó a los niños y propusieron que el contorno
fuese un cuadrado. Haga las modificaciones necesarias en el diseño para satisfacer la
nueva solicitud. De paso, evalúe la facilidad que ofrece su diseño anterior para ajustarse
a este cambio.
Una solución posible, consiste en reutilizar el diseño anterior modificando el
atributo contorno de la clase Cara para que sea un objeto de la clase Rectángulo en
lugar de Círculo. Como ambas clases, Rectángulo y Círculo, tienen un método llamado
paint(), no sería necesario modificar el método paint() de Cara, ya que el mensaje
contorno.paint() sigue siendo válido.
147
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
La búsqueda de la plasticidad, un cambio del diseño
El cambio o reajuste de requisitos es un fenómeno muy frecuente en el software,
tanto que resulta ser uno de los problemas más importantes (y también rentables) del
desarrollo de software.
La solución anterior al ejercicio de las caras resolvió la situación de modo
directo, sin pensar más allá, porque lo que no se sabe, no se sabe. De lo contrario en el
software se contratarían oráculos, videntes, cualquiera que sea capaz de anticiparse (con
exactitud), pero no dan resultado. La solución anterior, sin otro conocimiento, está bien.
Ahora han variado las condiciones y ya se sabe que se quiere modificar el
contorno. Pero, si hay cambios en el contorno podrá haber cambios en cualquier otro
atributo de la cara. Entonces, conviene diseñar una solución de mayor plasticidad
(deformable), que admita como componente de la cara cualquier figura. Se trata de
introducir ambigüedad en el diseño.
Un paso en esa dirección es asignar los atributos contorno, ojoizquierdo,
ojoderecho y boca de tipo Figura, usando la palabra tipo en el sentido estricto de
Liskov. La intención es aprovechar la posibilidad de polimorfismo que ofrecen las
operaciones de la clase Figura.
El diseño de los componentes de la cara como objetos de la clase Figura
introduce suficiente ambigüedad para componer la cara con cualquier figura de las
disponibles actualmente o en el futuro.
Otro paso que aumenta la plasticidad del diseño es agrupar los componentes de
la cara en un vector (facciones) con la finalidad de facilitar la uniformidad del
tratamiento. El uso de un vector de componentes añade ambigüedad al diseño porque
los nombres de los componentes se sustituyen por ordinales: el primer componente, el
segundo, etc. Gracias a esta ambigüedad se gana en igualdad de proceso y en facilidad
de modificación. Pero la ambigüedad también puede dificultar la comprensión por un
efecto de desconcierto. Casi nada es gratis. Para reducir el posible efecto negativo de la
ambigüedad se han utilizado variables temporales con los nombres de los componentes.
Sin embargo, a pesar de toda la ambigüedad introducida en el diseño, se
mantienen dependencias directas de Cara hacia Círculo y Rectángulo a causa de las
148
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
operaciones de creación de objetos. Sólo cuando los creamos necesitamos declarar su
tipo específico Círculo y Rectángulo (recordemos que no pueden crease objetos de
clases abstractas como Figura). En la aproximación a los patrones se verá una solución
que reducen estas ligaduras.
Una vez creados los objetos podemos tratarlos a todos por igual usando los
métodos de la clase Figura. Por eso ahora, en el método paint() de Cara utilizamos un
objeto de tipo Figura para recorrer el vector facciones y pintar cada uno de sus
elementos. Como todos los elementos de facciones son de tipo Figura siempre
podremos llamar al método paint() del elemento concreto utilizando la cabecera que
heredan todos de Figura.
La Figura 4. 23 muestra el diseño del diagrama de clases de esta otra nueva
solución de más plasticidad.
Ventana
-Cara cara
+Ventana()
-asignarLookAndFeel()
-configurarGUI()
-setCloseClick()
+paint(entrada Graphics g)
+main(entrada String[] args)
Cara
-Figura [ ] facciones
-int centrox
-int centroy
-int tamanyo
-Color color
+Cara()
+paint(entrada Graphics g)
Figura
Circulo
Rectangulo
Triangulo
Figura 4. 23 Diagrama de clases de Pintar Cara polimórfico
El diseño de la Figura 4. 23 aumenta la facilidad de extensión y modificación del
sistema (plasticidad) pero conserva la función del sistema; todavía los contornos de las
caras son círculos. Este ejemplo ilustra la siguiente definición:
149
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
Se denomina factorización a las modificaciones que se realizan en los diseños
para mejorar alguna cualidad interna del sistema sin variar sus funciones.
Veamos el código Java de esta nueva clase Cara.
public class Cara extends JPanel{
private Vector facciones; //Vector de objetos Figura que contiene las facciones de la cara:
//contorno, ojoderecho, ojoizquierdo y boca
private int tamanyo=200; //tamaño de la cara
private int centrox=500; //coordenada x del centro de la cara
private int centroy=350; //coordenada y del centro de la cara
private Color color=Color.YELLOW; //color de fondo de la cara
/** Crear una nueva instancia de Cara */
public Cara() {
Figura contorno, ojoizquierdo, ojoderecho, boca;
facciones = new Vector();
//crear el contorno y añadirlo a las facciones
contorno = new Circulo(centrox, centroy, tamanyo, color);
facciones.add(contorno);
//crear los ojos y añadirlo a las facciones
ojoizquierdo = new Circulo(centrox-(tamanyo/3), centroy-(tamanyo/4), 10, Color.BLACK );
facciones.add(ojoizquierdo);
ojoderecho = new Circulo(centrox+(tamanyo/3), centroy-(tamanyo/4), 10, Color.BLACK );
facciones.add(ojoderecho);
//crear la boca y añadirlo a las facciones
boca = new Rectangulo(centrox-(tamanyo/4), centroy+(tamanyo/2), tamanyo/2, 2,
Color.BLACK);
facciones.add(boca);
}
public void paint (Graphics g)
{
Figura figura;
super.paint(g); //lo utilizamos para pintar la ventana de Windows
//pintamos los elementos del vector facciones
Iterator i=facciones.iterator();
while (i.hasNext()){
figura= (Figura) i.next();
figura.paint(g);
}
}
}
150
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
La solución del rectángulo
Dada la plasticidad del diseño nuevo, el cambio del contorno de la cara se
resuelve sólo modificando la línea en la que creamos el objeto contorno para crear un
Rectángulo en lugar de un Círculo. Veamos este cambio sobre el código.
public class Cara extends JPanel{
private Vector facciones; //Vector de objetos Figura que contiene las facciones de la cara:
//contorno, ojoderecho, ojoizquierdo y boca
private int tamanyo=200; //tamaño de la cara
private int centrox=500; //coordenada x del centro de la cara
private int centroy=350; //coordenada y del centro de la cara
private Color color=Color.YELLOW; //color de fondo de la cara
/** Crear una nueva instancia de Cara */
public Cara() {
Figura contorno, ojoizquierdo, ojoderecho, boca;
facciones = new Vector();
//crear el contorno y añadirlo a las facciones
contorno = new Rectangulo(centrox-tamanyo,centroy-tamanyo,2*tamanyo,2*tamanyo,color);
facciones.add(contorno);
//crear los ojos y añadirlo a las facciones
ojoizquierdo = new Circulo(centrox-(tamanyo/3), centroy-(tamanyo/4), 10, Color.BLACK );
facciones.add(ojoizquierdo);
ojoderecho = new Circulo(centrox+(tamanyo/3), centroy-(tamanyo/4), 10, Color.BLACK );
facciones.add(ojoderecho);
//crear la boca y añadirlo a las facciones
boca = new Rectangulo(centrox-(tamanyo/4), centroy+(tamanyo/2), tamanyo/2, 2, Color.BLACK);
facciones.add(boca);
}
public void paint (Graphics g)
{
Figura figura;
super.paint(g); //lo utilizamos para pintar la ventana de Windows
//pintamos los elementos del vector facciones
Iterator i=facciones.iterator();
while (i.hasNext()){
figura= (Figura) i.next();
figura.paint(g);
}
}
}
151
Curso de OO dirigido por
la introducción de ambigüedad
La herencia, más aumento de la ambigüedad
La Figura 4. 24 muestra el resultado de la ejecución del programa.
Figura 4. 24 Resultado de Pintar Cara
Bibliografía
[Booch 94] Grady Booch, “Object Oriented Analysis and Design” Ed. Benjamin
Cummings Publishing, 1994
[Rumbaugh 95] James Rumbaugh et al. “Modelado y Diseño Orientado a
Objetos” Ed. Prentice Hall, 1995
[Liskov 86] Barbara Liskov et al. “Abstraction and Specification in Program
Development” Cambridge, MA: The MIT Press, 1988
[Fowler 97] Martin Fowler, “Analysis Patterns” Ed. Addison-Wesley, 1997
152
Descargar