Está en la página 1de 36

MÓDULO 2: MODELADO ORIENTADO A OBJETOS

Contenido
Creando Modelos en el Diseño de Software ................................................... 2

Evolución de los Lenguajes de Programación .................................................. 4

Cuatro principios de diseño ........................................................................... 11

Abstracción .................................................................................................... 11

Encapsulación ................................................................................................ 13

Descomposición ............................................................................................. 15

Generalización ............................................................................................... 16

Estructura de diseño en Java y diagramas de clase UML .............................. 18

Abstracción .................................................................................................... 18

Encapsulación ................................................................................................ 21

Descomposición ............................................................................................. 22

Generalización ............................................................................................... 26
Al finalizar este módulo, podrá:

(a) Describir los problemas en la creación de modelos para el diseño.


(b) Comprender cómo los lenguajes de programación evolucionaron
hacia la orientación a objetos.
(c) Explicar los cuatro principios de diseño principales utilizados en el
modelado orientado a objetos:
a. Abstracción
b. Encapsulación
c. C. Descomposición
d. Generalización
(d) Expresar los principios de diseño anteriores usando diagramas de
clases UML y código Java.
(e) Explicar y expresar la herencia de implementación.
(f) Explicar y expresar la herencia de interfaz.

El módulo anterior brindó una introducción a la importancia del diseño en el


proceso de desarrollo de software y terminó explicando la ventaja de usar
tarjetas CRC para completar un diseño conceptual.

Este módulo explorará aún más el modelado orientado a objetos. Comenzará


examinando problemas de modelado y cómo evolucionaron los lenguajes de
programación hacia la orientación a objetos. Luego, se discutirán los cuatro
principios principales de diseño de abstracción, encapsulación,
descomposición y generalización. Estos principios ayudan en la resolución de
problemas y conducen al desarrollo de software que es flexible, reutilizable
y mantenible. Son principios clave a seguir para desarrollar un buen diseño
para su software.

Este módulo también explorará cómo expresar la estructura de diseño en


código Java y diagramas de clase UML utilizando los principios de abstracción,
encapsulación y descomposición. Finalmente, discutirá la implementación y
la herencia de la interfaz dentro del principio de diseño de generalización.

Creando Modelos en el Diseño de Software


Cuando se trabaja en un proyecto de desarrollo de software, es importante
no saltar directamente a la creación de código para resolver el problema. En
cambio, hacer el producto correcto implica comprender todos los requisitos
de su producto y utilizar un buen diseño.

El paso del diseño se encuentra entre la comprensión de sus requisitos y la


construcción del producto. Trata iterativamente tanto el espacio del problema
como el espacio de la solución. El diseño también debe presentar y describir
conceptos de una manera que tanto los usuarios como los desarrolladores
entiendan, para que puedan discutir usando términos comunes.

El diseño es un paso tan importante en el desarrollo de software, y se han


desarrollado muchos enfoques a lo largo del tiempo para ayudar a que este
proceso sea más fácil. Por ejemplo, se han creado algunas estrategias de
diseño y lenguajes de programación para tipos específicos de problemas.

Un enfoque para facilitar el proceso de diseño es el enfoque orientado a


objetos. Esto permite la descripción de conceptos en los espacios de
problemas y soluciones como objetos: los objetos son una noción que pueden
entender tanto los usuarios como los desarrolladores, porque el pensamiento
orientado a objetos se aplica a muchos campos. Este conocimiento
compartido hace posible que los usuarios y desarrolladores discutan
elementos de problemas complejos. La programación orientada a objetos con
lenguajes orientados a objetos es, por lo tanto, un medio popular para
resolver problemas complejos.

Un buen diseño no salta simplemente de un concepto dentro del espacio del


problema para tratarlo en el espacio de la solución. El diseño orientado a
objetos no es una excepción. El diseño orientado a objetos consiste en:
• El diseño conceptual usa el análisis orientado a objetos para
identificar los objetos clave en el problema y descompone el
problema en partes manejables.
• El diseño técnico utiliza el diseño orientado a objetos para refinar
aún más los detalles de los objetos, incluidos sus atributos y
comportamientos, de modo que sea lo suficientemente claro para
que los desarrolladores lo implementen como software funcional.
Estas actividades de diseño ocurren de forma iterativa y continua.

El objetivo durante el diseño de software es construir y refinar "modelos" de


todos los objetos del software. Las categorías de objetos implican:
• objetos de entidad, donde el enfoque inicial durante el
diseño se coloca en el espacio del problema
• objetos de control que reciben eventos y coordinar acciones
a medida que el proceso avanza hacia el espacio de la solución
• objetos de contorno, limite o frontera que conectan
servicios externos a su sistema, a medida que el proceso avanza hacia
el espacio de la solución

Los modelos de software lo ayudan a comprender y organizar el proceso de


diseño de los objetos. Los principios y directrices de diseño se aplican a
problemas complejos: para simplificar objetos en el modelo y dividirlos en
partes más pequeñas y para buscar puntos en común que puedan manejarse
de forma coherente. Los modelos deben criticarse y evaluarse
continuamente para garantizar que se aborde el problema original y se
satisfagan cualidades como la reutilización, la flexibilidad y la mantenibilidad.
Los modelos también sirven como documentación de diseño para su
software. De hecho, los modelos a menudo se asignan al código fuente
esquelético, particularmente para un lenguaje orientado a objetos como
Java.

Los modelos de software a menudo se expresan en una notación visual,


llamada Lenguaje de modelado unificado (UML). El modelado orientado a
objetos tiene diferentes tipos de modelos o diagramas UML que se pueden
usar para enfocarse en diferentes problemas de software. Por ejemplo, se
podría usar un modelo estructural para describir qué hacen los objetos y
cómo se relacionan. Esto es análogo a un modelo a escala de un edificio, que
se utiliza en arquitectura.

Ahora que comprende los roles que juegan los modelos en el diseño y la
relación entre los modelos y los lenguajes de codificación, la próxima lección
se centrará en la revisión de la historia de los lenguajes de programación.

Evolución de los Lenguajes de Programación

Lenguaje es la palabra que usamos para describir un sistema para comunicar


pensamientos e ideas entre sí. ¡Escribir, leer, hablar, hacer dibujos y hacer
gestos son parte del lenguaje! Los idiomas deben evolucionar continuamente
para mantenerse "vivos" y ser utilizados por las personas.

Los lenguajes de programación no son una excepción y, al igual que los


lenguajes tradicionales, han evolucionado con el tiempo. A menudo, los
lenguajes de programación evolucionaron para brindar soluciones o
soluciones más efectivas a necesidades o problemas que los lenguajes de
programación actuales no pueden satisfacer. También pueden surgir nuevos
lenguajes o ideas para abordar nuevas estructuras de datos. Las ideas
utilizadas en los lenguajes de programación provocaron cambios en los
paradigmas de programación.

¿SABÍAS?

Un ejemplo de estrategias de diseño y lenguajes de programación


adecuados para tipos específicos de problemas con los que puede estar
familiarizado es la programación de arriba hacia abajo.

La programación de arriba hacia abajo se usa generalmente para resolver


problemas de procesamiento de datos. Esta estrategia de diseño consiste
en mapear los procesos del problema a las rutinas que se llamarán,
comenzando con el proceso "superior". Generalmente, este diseño se
expresa a través de un árbol de rutinas.

Estas rutinas se implementarían en un lenguaje de programación que


admitiera subrutinas.

Es importante conocer la historia de los paradigmas de programación. Como


desarrollador de software, es posible que aún encuentre sistemas que
utilicen lenguajes y paradigmas de diseño más antiguos. Además, aunque la
programación orientada a objetos es una herramienta poderosa, puede
haber problemas que se resuelvan mejor o más eficientemente con otro
paradigma. Finalmente, es importante entender esta historia, ya que los
nuevos lenguajes pueden no forzar nuevas estructuras sino solo modificar las
existentes. Algunas viejas formas de hacer algo o viejos paradigmas pueden
expandirse tanto que las nuevas estructuras pueden ser difíciles de
reconocer. El conocimiento del pasado puede ayudar.
A continuación, se muestra una tabla que resume los principales paradigmas de programación en la historia de los
lenguajes de programación.

Lenguaje de Periodo Soluciones que ofrece el Lenguaje de Programación Cuestiones no resueltas del
Programación de tiempo lenguaje de programación
COBOL 1960s COBOL y Fortran siguieron un paradigma imperativo que dividía los programas Si se realizan cambios en los
Fortran grandes en programas más pequeños llamados subrutinas. datos, es posible que las
subrutinas se encuentren con
Como el tiempo de procesamiento de la computadora era costoso, era importante casos en los que los datos
maximizar el rendimiento del procesamiento. Para resolver este problema, se usaron globales no sean los
datos globales para que los datos se ubicaran en un solo lugar en la memoria de la esperados. Se necesita una
computadora y fueran accesibles desde cualquier lugar para un programa. Esto mejor gestión de datos para
significaba que las subrutinas solo tenían que ir a un lugar para acceder a las evitar estos problemas.
variables.

Subprograma
Subprograma B
Subprograma
A
C

Datos
Globales
Lenguaje de Periodo Soluciones que ofrece el Lenguaje de Programación Cuestiones no resueltas del
Programación de tiempo lenguaje de programación
Algol 68 Principios En la década de 1960, se utilizaron datos globales. Sin embargo, cualquier cambio en Hacia mediados de la década
Pascal de la los datos puede generar problemas para las subrutinas. de 1970, el tiempo de
década de La solución introdujo la idea de ámbitos y variables locales: las subrutinas o los procesamiento de la
1970 procedimientos podían tener cada uno sus propias variables. computadora se volvió menos
costoso. Al mismo tiempo, la
mano de obra humana era
más costosa y se convirtió en
el factor que consumía más
tiempo en el desarrollo de
software. Los avances en el
procesamiento informático
permitieron plantear
problemas más complejos a
las computadoras. Pero
Estos lenguajes admitían el uso de tipos de datos abstractos, definidos por el también significaba que el
programador y no integrados en el lenguaje. Esta es una agrupación de información software estaba creciendo
relacionada que se denota con un tipo. Esto permite que la información sea organizada rápidamente y tener un
de una manera significativa. archivo para mantener los
Al tener datos agrupados y pasados a diferentes procedimientos mediante el uso de programas era difícil de
tipos de datos, esto significa que un procedimiento puede ser el único que modifica mantener.
una parte de los datos. Ya no es necesario preocuparse de que los datos sean alterados
por otro procedimiento.
Lenguaje de Periodo de Soluciones que ofrece el Lenguaje de Programación Cuestiones no resueltas del lenguaje
Programación tiempo de programación
C Mediados A mediados de la década de 1970, las computadoras eran más rápidas y No es fácil que un tipo de datos
Modula-2 de la capaces de abordar problemas más complejos. Sin embargo, esto significaba abstracto herede de otro en estos
década de que los programas del pasado se estaban volviendo demasiado grandes para lenguajes. Esto significa que aunque
1970 mantenerlos. Esto condujo a nuevos lenguajes, que proporcionaron un se pueden crear tantos tipos de
medio para organizar programas en archivos separados y permitieron a los datos como se desee, no se puede
desarrolladores crear más fácilmente copias múltiples, pero únicas, de tipos declarar un tipo como extensión de
de datos abstractos. otro.

Por ejemplo, en el lenguaje de programación C, cada archivo contenía todos


los datos asociados y las funciones que lo manipulaban, y declaraba a qué se
podía acceder a través de un archivo separado llamado archivo de
encabezado.
Lenguaje de Periodo de Soluciones que ofrece el Lenguaje de Programación Cuestiones no resueltas del lenguaje
Programación tiempo de programación
Programación 1980 al Aunque los programas se volvieron más fáciles de administrar a través de La programación orientada a objetos
Orientada a presente tipos de datos abstractos, todavía no había forma de que los tipos de datos es el paradigma de programación
Objetos (Java, se heredaran unos de otros. Los conceptos de diseño orientado a objetos predominante ahora.
C++, C#, etc.) se hicieron populares durante este período de tiempo como una solución a
estos problemas.

El diseño orientado a objetos busca:


• hacer que un tipo de datos abstracto sea más fácil de escribir
• estructurar un sistema alrededor de tipos de datos abstractos
llamados clases
• introducir la capacidad de un tipo de datos abstractos para
extender otro a través de un concepto conocido como herencia
Bajo este paradigma, los sistemas de software pueden construirse a partir
de tipos de datos completamente abstractos. Esto permite que el sistema
imite la estructura del problema; en otras palabras, el sistema puede
representar objetos o ideas del mundo real con mayor precisión.

Los archivos de definición de clases en la programación orientada a objetos


reemplazan los archivos en C y Modula-2. Cada clase define un tipo con
datos y funciones asociados. Estas funciones también se conocen como
métodos. Una clase actúa como una fábrica, haciendo que los objetos
individuales sean todos de un tipo específico. Esto permite compartimentar
y manipular los datos en sus propias clases separadas.
Cuatro principios de diseño
Como se ha descrito previamente la programación orientada a objetos
permite crear modelos de cómo se representan los objetos en su sistema. Sin
embargo, para crear un programa orientado a objetos, debe examinar los
principales principios de diseño de dichos programas. Cuatro de estos
principios principales son: abstracción, encapsulación, descomposición y
generalización.

Abstracción

La abstracción es uno de los cuatro principios de diseño principales que se


examinarán en esta sección. La abstracción es una de las principales formas
en que los humanos lidian con la complejidad. Es la idea de simplificar un
concepto en el dominio del problema. La abstracción descompone un
concepto en una descripción simplificada que ignora los detalles sin
importancia y enfatiza los elementos esenciales necesarios para el concepto,
dentro de algún contexto.

Una abstracción debe seguir la regla del menor asombro. Esta regla sugiere
que los atributos y comportamientos esenciales deben capturarse sin
sorpresas ni definiciones que queden fuera de su alcance. Esto evita que las
características irrelevantes se conviertan en parte de una abstracción y ayuda
a garantizar que la abstracción tenga sentido para el propósito del concepto.

Las construcciones del programa incluyen elementos como funciones, clases,


enumeraciones y métodos. En el modelado orientado a objetos, la abstracción
se relaciona más directamente con la noción de clase. Cuando se usa la
abstracción para determinar los detalles esenciales de algún concepto, esos
detalles pueden definirse en una clase. Cualquier objeto creado a partir de
una clase tiene los detalles esenciales para representar una instancia de algún
concepto, pero también puede tener algunas características individuales.
Piense en un cortador de galletas que se usa para crear hombres de pan de
jengibre. Cada instancia de una galleta cortada pertenece a la clase de
"hombres de pan de jengibre" y comparte características esenciales como la
cabeza, los brazos y las piernas, incluso si están decorados de manera
diferente.

El contexto o una perspectiva específica es fundamental al formar una


abstracción. Esto se debe a que el contexto puede cambiar las características
esenciales de un concepto. Por ejemplo, considere las características
esenciales del concepto de persona. Esto puede ser difícil de entender sin
contexto, ya que este concepto es vago y se desconoce el propósito de la
persona. Pero, en una aplicación de juegos, las características esenciales de
una persona estarían en el contexto de un jugador. En una aplicación de
ejercicio de carrera, por otro lado, las características esenciales de una
persona estarían en el contexto de un atleta. Depende del diseñador elegir la
abstracción que sea más apropiada para el contexto del desarrollo del
software, y el contexto debe entenderse antes de crear una abstracción.

Las características esenciales de una abstracción pueden entenderse de dos


formas: a través de atributos básicos ya través de comportamientos o
responsabilidades básicos.

Los atributos básicos son características que no


desaparecen con el tiempo. Aunque sus valores
pueden cambiar, los atributos en sí no lo hacen. Por
ejemplo, el concepto de león puede tener un
atributo de edad. Ese valor puede cambiar, pero el
león siempre tiene un atributo de edad.
Además de los atributos básicos, una abstracción
describe los comportamientos básicos de un
concepto. Un león puede tener comportamientos
como cazar, comer y dormir. Estas también son
responsabilidades que la abstracción del león tiene
para su propósito de vivir.

Una abstracción, como se explicó anteriormente, solo debe transmitir los


atributos y comportamientos esenciales de un concepto. El contexto ayuda a
determinar qué es relevante. Por ejemplo, al considerar al león en un
escenario de caza, es irrelevante considerar en qué posición prefiere dormir
el león. Si el contexto cambia, la abstracción correcta también puede
cambiar.

Hay muchos beneficios del principio de abstracción. Ayuda a simplificar los


diseños de las clases, para que sean más enfocados, breves y comprensibles
para otra persona que los vea. Dado que las abstracciones dependen en gran
medida del contexto o la perspectiva, es importante considerar
detenidamente qué es relevante. Del mismo modo, si el propósito del
sistema que se está construyendo, o si el problema que se está resolviendo
cambia, es importante volver a examinar sus abstracciones y cambiarlas en
consecuencia.
Encapsulación

La encapsulación o encapsulamiento es el segundo principio de diseño


principal que se examinará en esta sección. Este principio involucra un
concepto que permite que algo esté contenido en una cápsula, a algunos de
los cuales puedes acceder desde el exterior y a otros no.

Hay tres ideas detrás de la encapsulación. Estas son:


1. La capacidad de "agrupar" valores de atributos (o datos) y
comportamientos (o funciones) que manipulan esos valores, en un
objeto autónomo.
2. La capacidad de "exponer" ciertos datos y funciones de ese objeto, a
los que se puede acceder desde otros objetos, generalmente a
través de una interfaz.
3. La capacidad de "restringir" el acceso a ciertos datos y funciones solo
dentro del objeto.
La "agrupación" ocurre naturalmente cuando se define una clase para un tipo
de objeto. El principio de abstracción ayuda a determinar qué atributos y
comportamientos son relevantes sobre un concepto en un contexto
determinado. El principio de encapsulación lleva esto un paso más allá y
asegura que estas características se agrupan en la misma clase.

Por lo tanto, la encapsulación permite que distintos objetos creados a partir


de una clase en particular tengan sus propios valores de datos para los
atributos y exhiban comportamientos resultantes. Esto hace que la
programación sea mucho más fácil, ya que los datos y el código que manipula
esos datos se encuentran en el mismo lugar.

Los datos de un objeto solo deben contener lo que es relevante para ese
objeto. Por ejemplo, un objeto león “sabe” qué comida caza pero no sabe qué
animales viven en un continente diferente, porque esos no son datos
relevantes. Por lo tanto, una clase solo sabe qué atributos o datos son
relevantes para ella.

Una clase también define comportamientos a través de métodos. Los


métodos manipulan los valores de los atributos o los datos en el objeto para
lograr los comportamientos reales. Ciertos "métodos" pueden exponerse o
hacerse accesibles a objetos de otras clases. Esto proporciona una interfaz a
otros objetos para usar la clase.
Integridad y Seguridad

Como una de las ideas de la encapsulación es restringir el acceso a ciertos


datos y funciones dentro de un solo objeto, esto vincula naturalmente la
encapsulación con la integridad de los datos y la seguridad de la información
confidencial.

Si ciertos atributos y métodos tienen restringido el acceso externo, excepto a


través de métodos específicos, los datos no se pueden cambiar mediante
asignaciones de variables.

Asimismo, la restricción de acceso ayuda a evitar que se revele información


confidencial, incluso de consultas que se basan en datos confidenciales para
brindar respuestas.

Implementación modificable

La encapsulación también es un principio útil para implementar cambios de


software. Como la capacidad de “exponer” los datos es independiente del
“paquete” de atributos en sí, esto significa que la implementación de
atributos y métodos puede cambiar, pero la interfaz accesible de una clase
puede permanecer igual. Los usuarios que acceden o consultan la clase no
necesitan preocuparse por cómo funciona la implementación detrás de la
interfaz; — Ellos seguirán utilizando los mismos medios para acceder a la
información.

Caja negra

La idea de encapsulación que respalda la línea de pensamiento en la


implementación cambiante también está vinculada a un concepto conocido
como pensamiento de caja negra. Los pasos de cálculo tomados dentro de
una clase nunca necesitan ser conocidos por ninguna otra clase, siempre que
puedan acceder a la interfaz. Por lo tanto, una clase es como una caja negra
en la que no puede ver los detalles sobre cómo se representan los atributos
o cómo los métodos calculan sus resultados. Lo que suceda en la “caja” para
lograr un comportamiento esperado no importa, siempre que sea posible
proporcionar entradas y obtener salidas llamando a los métodos.

La encapsulación logra una barrera de abstracción a través del pensamiento


de caja negra donde el funcionamiento interno de una clase no es relevante
para el mundo exterior. Esto da como resultado una abstracción que reduce
la complejidad para los usuarios de la clase.

La encapsulación también aumenta la reutilización debido al pensamiento de


caja negra. Otra clase solo necesita saber el método correcto a llamar para
obtener el comportamiento deseado, qué argumentos proporcionar como
entradas y qué aparece como salidas o efectos. En otras palabras, la
encapsulación mantiene el software modular y fácil de usar. Las clases son
fáciles de administrar, ya que su comportamiento interno no es relevante para
otras clases, siempre que puedan interactuar entre sí.

Descomposición

La descomposición es el tercer principio principal de diseño que se examinará


en esta sección. Consiste en tomar una cosa entera y dividirla en diferentes
partes. Alternativamente, la descomposición también puede indicar, tomar
partes separadas con diferentes funcionalidades y combinarlas para crear un
todo. La descomposición permite dividir los problemas en partes más
pequeñas que son más fáciles de entender y resolver.

La regla general para la descomposición es observar las diferentes


responsabilidades de un todo y evaluar cómo se puede separar el todo en
partes, cada una de las cuales tiene una responsabilidad específica. De hecho,
cada una de estas partes son objetos separados que se pueden crear a partir
de clases separadas en su diseño. De esta manera, la descomposición es
similar a la abstracción en la que se divide un todo en objetos con
características esenciales.

Cada tipo diferente de parte dentro de un todo puede prescribir una clase,
por lo que podemos mantener nuestras partes mejor organizadas y
encapsuladas por sí mismas. La clase del objeto completo se relaciona
entonces con las clases de los objetos parciales constituyentes.

La naturaleza de las partes

Un todo puede tener un número fijo o dinámico de cierto tipo de parte. Si hay
un número fijo, entonces durante la vida útil del objeto completo, tendrá
exactamente esa cantidad del objeto parcial. Piense en un horno con cuatro
quemadores. El número de quemadores es fijo para el objeto del horno.
Algunas partes, por otro lado, pueden tener un número dinámico. Esto
significa que todo el objeto puede obtener nuevas instancias de esos objetos
parciales durante su vida útil. Piense en artículos de comida dentro de un
objeto refrigerador; estos pueden cambiar de un día a otro.

Tenga en cuenta que una parte también puede servir como un todo, que se
compone de otras partes constituyentes. Por ejemplo, una cocina es una parte
de una casa. Pero la cocina puede estar compuesta por otras partes, como un
horno y un frigorífico.

Otro problema que vale la pena señalar en la descomposición es que los


objetos completos y los objetos parciales tienen vidas. A veces, estas vidas
están estrechamente relacionadas y la parte comparte la misma vida que el
todo: una no puede existir sin la otra. Si el indicador de temperatura de
enfriamiento de un refrigerador muere, el refrigerador dejará de funcionar.
Pero a veces, la parte y el todo pueden existir de forma independiente y tener
vidas diferentes. Por ejemplo, si un alimento se estropea en el frigorífico, el
frigorífico seguirá funcionando.

Las cosas enteras también pueden contener partes que se comparten con
otro todo al mismo tiempo. Sin embargo, a veces compartir una parte no es
posible o no es la intención.

Generalización

La generalización es el último de los cuatro principios principales de diseño


que se examinarán en esta sección. La generalización ayuda a reducir la
redundancia al resolver problemas. Es un principio común utilizado en muchas
disciplinas fuera del desarrollo de software.

En la codificación, los comportamientos algorítmicos a menudo se modelan a


través de métodos. Un método permite a un programador generalizar un
comportamiento, por lo que el comportamiento se puede aplicar a diferentes
datos de entrada. Esta generalidad reduce la necesidad de tener un código
idéntico en todo el programa.

En el modelo orientado a objetos, la generalización es un principio


fundamental del diseño, pero más allá de crear un método que se pueda
aplicar a diferentes datos, el modelo orientado a objetos logra la
generalización por clases a través de la herencia. En la generalización,
tomamos características repetidas, comunes o compartidas entre dos o más
clases y las factorizamos en otra clase.
Esto le permite tener dos tipos de clases: una clase principal y una clase
secundaria. Las clases secundarias heredan atributos y comportamientos de
las clases principales. Esto significa que las características repetidas, comunes
o compartidas van a las clases principales. Las clases para padres capturan
ideas generales y generalmente tienen una aplicación más amplia.

Es posible que varias clases secundarias hereden de una sola clase principal.
Esas múltiples clases secundarias recibirán todos estos atributos y
comportamientos comunes, aunque es probable que cada clase secundaria
tenga atributos y comportamientos adicionales que les permitan
especializarse más en lo que pueden hacer.

En la terminología estándar, una clase principal se conoce como superclase y


una clase secundaria se denomina subclase. De la explicación anterior,
podemos entender que la herencia permite que una superclase forme una
generalización y que sus subclases sean más especializadas.

Las clases principales ahorran tiempo y evitan errores, especialmente cuando


se usan para varias clases secundarias. Sin clases principales, los sistemas no
son flexibles, mantenibles o reutilizables.

Nota:
Un buen consejo para recordar al nombrar superclases y subclases:
aunque las clases pueden recibir el nombre que desee, es una buena
práctica nombrarlas según las cosas que está tratando de modelar.
¡Esto hace que el código sea más fácil de entender!

La generalización presenta muchas ventajas para el modelado orientado a


objetos. Dado que las subclases heredan atributos y comportamientos de las
superclases, esto significa que cualquier cambio en el código que sea común
a ambas subclases solo debe realizarse una vez en la superclase. En otras
palabras, los cambios en el software son más fáciles de aplicar y mantener.
Otra ventaja es que las subclases se pueden agregar fácilmente sin tener que
recrear todos los atributos y comportamientos comunes para ellas, por lo que
el software es más fácil de expandir. La generalización proporciona soluciones
de software más sólidas y permite un código más reutilizable porque los
mismos bloques de código se pueden usar para diferentes clases.
¿SABÍAS?
Tanto los métodos como la herencia ejemplifican el principio de diseño de
generalización a través de D.R.Y. o la regla “No te repitas a ti mismo”. Los
métodos y la herencia permiten a los desarrolladores reutilizar el código,
lo que genera menos código y menos repeticiones en general.

Estructura de diseño en Java y diagramas de clase UML


El proceso de diseño, como se explicó en el Módulo 1, consta tanto del diseño
conceptual como del diseño técnico. El diseño conceptual, incluida la creación
de prototipos y la simulación de diseños de alto nivel, se puede visualizar a
través de tarjetas CRC. Las tarjetas CRC facilitan la comunicación con el cliente
y le permiten crear diseños sin la distracción del código. Sin embargo, para
guiar el diseño técnico, se necesita una técnica más sofisticada que pueda
comunicar claramente sus necesidades a los desarrolladores. Una técnica
utilizada para el diseño técnico es la del diagrama de clases UML, también
conocido simplemente como diagrama de clases. Estos diagramas de clases
brindan más detalles que las tarjetas CRC y permiten una conversión más fácil
a clases para la codificación y la implementación.

Esta sección analizará cómo funcionan los principios de diseño de


abstracción, encapsulación, descomposición y generalización trabajando con
Java y diagramas de clases UML.

Abstracción

El principio de diseño de la abstracción permite la simplificación de un


concepto a sus elementos esenciales dentro de algún contexto. La
abstracción se puede aplicar a nivel de diseño utilizando diagramas de clases
UML. El diseño finalmente se convierte en código.

Las tarjetas CRC capturan componentes en el diseño de sistemas. Los


componentes se pueden refinar eventualmente en funciones, clases o
colecciones de otros componentes. Para esta clase se utilizará Java, donde las
abstracciones se forman en una clase, esta sección se centrará en las clases.

Veamos cómo una tarjeta CRC podría traducirse en un diagrama de clases. A


continuación, se muestra un ejemplo de una tarjeta CRC, como abstraer para
un ítem alimento en el contexto de un supermercado.
Alimento
Saber ID de supermercado
Saber el nombre
Saber el fabricante
Saber la fecha de caducidad
Saber el precio
Consultar si está en oferta

Este es el mismo concepto que un diagrama de clases.

Alimento
idSupermercado: String
nombre: String
fabricante: String
fechaCaducidad: Date
precio: double
esOferta (): boolean

Cada concepto o clase en un diagrama de clases se representa con un cuadro,


como se muestra arriba. Notarás tres secciones en el cuadro.

Nombre de la clase

Propiedades

Operaciones

• El nombre de la clase es el mismo que el nombre de la clase en su


clase de Java.
• La sección de propiedades es equivalente a las variables miembro de
Java. Esta sección define los atributos de la abstracción mediante el
uso de una plantilla estándar para el nombre y el tipo de variable. Los
tipos de variables pueden ser clases o tipos primitivos.

<variable name>:<variable type>


• La sección de operaciones es equivalente a los métodos de Java. Esta
sección define los comportamientos de la abstracción, utilizando una
plantilla estándar para el nombre de la operación, la lista de
parámetros y el tipo de devolución.

<name> (<parameter list>): <return type>


En el ejemplo anterior, los objetos de alimento tienen un método para
devolver si están en oferta o no. Este método ha sido denominado “esOferta”.
El método devolverá un valor booleano para representar si está en oferta. Un
valor booleano es verdadero o falso. La operación esOferta no toma ningún
parámetro, por lo que no se incluye ninguna lista de parámetros.

Si agregara un parámetro a la operación, como una fecha en este caso, el


parámetro seguiría la misma plantilla que las propiedades del diagrama de
clases. La sección final diría:

esOferta(date: Date) : Boolean

Notará que los diagramas de clases distinguen una diferencia entre las
responsabilidades que se convierten en propiedades y las responsabilidades
que se convierten en operaciones, mientras que las tarjetas CRC las
enumeran juntas. Ayudar a distinguir esta ambigüedad hace que los
diagramas de clases sean más fáciles de traducir a código para un
programador.

Basándonos en nuestro ejemplo de alimento, podemos ver lo fácil que es


convertir un diagrama de clases en una clase en Java.

public class Alimento {


public String idSupermercado;
public String nombre;
public String fabricante;
public Date fechaCaducidad;
public double precio;

public boolean esOferta( Date date ) {

}
}

El nombre de la clase en el diagrama de clases se convierte en una clase en


Java. Las propiedades se convierten en variables miembro. Las operaciones
se convierten en métodos. Es posible usar este mapeo a la inversa para
convertir el código en diagramas de clases.
Nota: el ejemplo anterior todos los elementos son públicos, lo que
significa que se puede acceder a las variables y métodos desde cualquier
otro código además de esta clase. Esta suposición se aplica por ahora.

Encapsulación

El principio de diseño de la encapsulación involucra tres ideas:

• Los datos y las funciones que manipulan esos datos se “agrupan” en


un objeto autónomo.
• Los datos y las funciones del objeto pueden exponerse o hacerse
accesibles desde otros objetos.
• Los datos y las funciones del objeto se pueden restringir solo dentro
del objeto.

En un diagrama de clases UML, la encapsulación se expresa al tener todos los


datos relevantes del objeto definidos en los atributos de la clase y al
proporcionar métodos específicos para acceder a esos atributos.

Los diagramas de clases UML pueden expresar encapsulación. El propio


diagrama de clases ya agrupa datos y funciones en un objeto autónomo. Sin
embargo, el acceso y la restricción (dos aspectos de la visibilidad) también se
pueden representar mediante el uso de símbolos – y +. A continuación, se
muestra un ejemplo de un diagrama de clases UML para un estudiante.

Estudiante
-promedio: float
-carrera: String

+getPromedio(): float
+setPromedio ( float )
+getCarrera (): String
+setCarrera( String )

En este ejemplo, los atributos promedio y carrera están ocultos al acceso


público, como lo indica el signo menos (-). En otras palabras, el signo menos
indica que un método o atributo es privado y solo se puede acceder desde
dentro de la clase. Por otro lado, las operaciones son públicas, como lo indica
el signo más (+). En otras palabras, el signo más indica que se puede acceder
públicamente a un método. En este caso, los métodos públicos pueden usarse
para manipular el promedio del estudiante. Esto evita que el atributo
promedio del estudiante se manipule directamente y controla el acceso y la
modificación de los datos.

La encapsulación en los diagramas de clase UML lo ayuda a determinar la


“puerta” para controlar los datos, utilizando solo métodos públicos para
acceder a los atributos de datos de la clase. Para cada pieza de datos
esenciales, el uso de métodos públicos para acceder a datos privados crea
protección contra cambios directos inesperados de esos datos. Esto preserva
la integridad de los datos.

Hay dos tipos diferentes de métodos que se utilizan normalmente para


preservar la integridad de los datos. Estos son:

• Métodos getter, que se utilizan para recuperar datos. Estos métodos


suelen tener el formato: get<Nombre del atributo>, donde el atributo
es el valor que se devolverá a través del método. Los getter a menudo
recuperan datos privados.

• Métodos setter, se utilizan para modificar los datos. Estos métodos


suelen tener el formato: set<Nombre del atributo>, donde el atributo
es lo que se cambiará a través del método. Los setters suelen
establecer un atributo privado de forma segura.

Este tipo de métodos ayudan a garantizar que se acceda a los datos de


manera aprobada.

Descomposición

El principio de diseño de la descomposición toma una cosa completa y la


divide en diferentes partes. También hace lo contrario, y toma partes
separadas con diferentes funcionalidades y las combina para formar un todo.
Hay tres tipos de relaciones en descomposición, que definen la interacción
entre el todo y las partes:

• Asociación
• agregación
• composición

Los tres son útiles y versátiles para el diseño de software. A continuación,


cada una de estas relaciones.
Asociación

La asociación indica una relación suelta entre dos objetos, que pueden
interactuar entre sí durante algún tiempo. No dependen el uno del otro; si se
destruye un objeto, el otro puede seguir existiendo y puede haber cualquier
cantidad de cada elemento en la relación. Un objeto no pertenece a otro y
pueden tener números que no están vinculados entre sí.

Un ejemplo de una relación de asociación podría ser una persona y un hotel.


Una persona puede interactuar con un hotel, pero no poseer uno. Un hotel
puede interactuar con muchas personas.

La asociación se representa en los diagramas UML de la siguiente manera:

Persona Hotel
0..* 0..*

La línea recta entre los dos objetos UML indica que existe una relación entre
los dos objetos UML de persona y hotel, y esa relación es una asociación. El
“cero, punto, punto, asterisco” (0...*) en el lado derecho de la línea muestra
que un objeto Persona está asociado con cero o más objetos Hotel, mientras
que la “cero, punto, punto, asterisco” en el lado izquierdo de la línea muestra
que un objeto Hotel está asociado con cero o más objetos Persona.

La asociación también se puede representar en código Java.

public class Estudiante {


public void jugar( Deporte deporte ){
execute.jugar( deporte );
}

}

En este extracto del código, a un estudiante se le pasa un objeto deporte para


jugar, pero el estudiante no posee el deporte. Solo interactúa con él para
jugar. Los dos objetos están completamente separados, pero tienen una
relación vaga. Un estudiante puede practicar cualquier número de deportes
y cualquier número de estudiantes puede practicar un deporte.
Agregación

La agregación es una relación de “tiene una” relación donde un todo tiene


partes que le pertenecen. Las partes pueden compartirse entre todos en esta
relación. Sin embargo, las relaciones de agregación suelen ser débiles. Esto
significa que, aunque las partes pueden pertenecer a todos, también pueden
existir de forma independiente. Un ejemplo de una relación agregada es la de
un avión comercial y su tripulación. El avión comercial no podría ofrecer
servicios sin la tripulación. Sin embargo, el avión comercial no deja de existir
si la tripulación se va. La tripulación tampoco deja de existir si no está en el
avión.

La agregación se puede representar en diagramas de clase UML con el


símbolo de un rombo vacío como se muestra a continuación:

En este diagrama, se vuelve a utilizar una línea recta para simbolizar una
relación entre el objeto Avión comercial y el objeto Miembro de la tripulación.
El “cero punto, punto, asterisco” (0..*) se utiliza para mostrar nuevamente
que un objeto puede tener una relación con cero o más del otro objeto. En
este caso, el “cero, punto, punto, asterisco” en el lado derecho de la línea
indica que un objeto Avión comercial podría tener cero o más miembros de la
tripulación. El “cero, punto, punto, asterisco” en el lado izquierdo de la línea
indica que cero o más objetos Avión comercial pueden tener un objeto
Miembro de la tripulación. El rombo vacío indica qué objeto se considera el
todo y no la parte en la relación.
La agregación también se puede representar en código Java.

public class AvionComercial {


private ArrayList<MiembroTripulacion> tripulacion;

public AvionComercial () {
tripulacion = new ArrayList<MiembroTripulacion>();
}
public void add( MiembroTripulacion miembroTripulacion) {

}
}

En la clase AvionComercial, hay una lista de miembros de la tripulación. La


lista de miembros de la tripulación se inicializa para estar vacía y un método
público permite agregar nuevos miembros de la tripulación. Un avión
comercial tiene tripulación. Esto significa que un avión comercial puede tener
cero o más tripulantes.

Composición

La composición es una de las relaciones de descomposición más


dependientes. Esta relación es una contención exclusiva de partes, también
conocida como una relación fuerte “tiene una”. En otras palabras, un todo no
puede existir sin sus partes, y si se destruye el todo, también se destruyen las
partes. En esta relación, normalmente solo puede acceder a las partes a
través de su totalidad. Las partes contenidas son exclusivas del todo. Un
ejemplo de una relación de composición es entre una casa y una habitación.
Una casa se compone de varias habitaciones, pero si elimina la casa, la
habitación ya no existe.

La composición se puede representar con un rombo relleno usando


diagramas de clase UML, como se muestra a continuación:
Las líneas entre el objeto Casa y el objeto Habitación indican una relación
entre los dos. El rombo relleno junto al objeto Casa significa que la casa es el
todo en la relación. Si el diamante está lleno, simboliza que la relación “tiene
una” es fuerte. Los dos objetos dejarían de existir el uno sin el otro. El “uno
punto, punto, asterisco” indica que debe haber uno o más objetos Habitación
para el objeto Casa.

La composición también se puede representar usando código Java.

public class Casa {


private Habitacion habitacion;

public Casa(){
habitacion = new Habitacion ();
}
}

En este ejemplo, un objeto Habitación se crea al mismo tiempo que el objeto


Casa, instanciando la clase Habitación. No es necesario crear este objeto
Habitación en otro lugar y no es necesario transferirlo al crear el objeto Casa.
Las dos partes son estrechamente dependientes y una no puede existir sin la
otra.

Generalización

El principio de diseño de generalización toma características repetidas,


comunes o compartidas entre dos o más clases y las factoriza en otra clase, de
modo que el código se puede reutilizar y las características pueden ser
heredadas por subclases.

La generalización y la herencia se pueden representar en diagramas de clases


UML mediante una flecha continua, como se muestra a continuación:
La flecha continua indica que dos clases están conectadas por herencia. La
superclase está en la punta de la flecha, mientras que la subclase está en la
cola. Es convencional tener la flecha apuntando hacia arriba. El diagrama de
clases está estructurado de modo que las superclases estén siempre en la
parte superior y las subclases en la parte inferior.

Los atributos y comportamientos de la superclase heredada no necesitan ser


reescritos en la subclase. En cambio, la flecha simboliza que la subclase tendrá
los atributos y métodos de la superclase. Las superclases son las clases
generalizadas y las subclases son las clases especializadas.

Es posible traducir diagramas de clases UML en código. Construyamos sobre


el ejemplo anterior.

Animal
#numeroPatas: int
#numeroColas: int
#nombre: String
+caminar()
+correr()
+comer()

Leon

+ rugir()
En este diagrama, la clase Leon es la subclase y la clase Animal es la superclase.
La subclase hereda todos los atributos y comportamientos de la superclase.
Como se explicó anteriormente, no es necesario colocar todos los atributos y
comportamientos de la superclase en la subclase del diagrama de clases UML.

También puede notar el uso de un símbolo #. Esto simboliza que los atributos
del Animal están protegidos.

Solo se puede acceder a los atributos protegidos en Java mediante:

• la propia clase encapsulada


• todas las subclases
• todas las clases dentro del mismo paquete

En Java, un paquete es una forma de organizar clases en un espacio de


nombres que representa esas clases.

Los diagramas de clases UML se pueden traducir a código.

public abstract class Animal {


protected int numeroPatas;
protected int numeroColas;
protected String nombre;

public Animal( String nombreMascota, int patas,


int colas ) {
this.nombre = nombreMascota;
this.numeroPatas = legs;
this.numeroColas = colas;
}

public void caminar() { … }


public void correr() { … }
public void comer() { … }
}

Dado que la clase Animal es una generalización, no debe crearse como un


objeto por sí solo. La palabra clave abstract indica que la clase no se puede
instanciar. En otras palabras, no se puede crear un objeto Animal.

La clase Animal es una superclase, y cualquier clase que herede de la clase


Animal tendrá sus atributos y comportamientos. Esas subclases que heredan
de la superclase compartirán los mismos atributos y comportamientos de la
clase Animal. A continuación el código para crear una subclase Leon:

public class Leon extends Animal {


public Leon( String nombre, int patas, int colas
) {
super( nombre, patas, colas );
}

public void rugir() { … }


}

No es necesario declarar ninguno de los atributos y comportamientos


heredados de la clase Animal. Esto refleja el diagrama de clases UML, ya que
solo se declaran atributos y métodos especializados en la superclase y la
subclase. Recuerde, el diagrama de clases UML representa nuestro diseño.
Como los atributos y comportamientos heredados no necesitan volver a
establecerse en el código, no es necesario volver a establecerse en la subclase
del diagrama.

La herencia se declara en Java utilizando la palabra clave extends. Los objetos


se instancian a partir de una clase mediante el uso de constructores. Con la
herencia, si desea una instancia de una subclase, debe darle a la superclase la
oportunidad de preparar los atributos para el objeto de manera adecuada.
Las clases pueden tener constructores implícitos o constructores explícitos.

A continuación se muestra un ejemplo de un constructor implícito:

public abstract class Animal {


protected int numeroPatas;

public void caminar() { … }


}

En esta implementación, no hemos escrito nuestro propio constructor. A


todos los atributos se les asigna cero o nulo cuando se usa el constructor
predeterminado.

A continuación, se muestra un ejemplo de un constructor explícito:

public abstract class Animal {


protected int numeroPatas;
public Animal( int patas ) {
this.numeroPatas = patas;
}
}

En esta implementación, un constructor explícito nos permitirá instanciar un


animal con tantas patas como queramos. Los constructores explícitos le
permiten asignar valores a los atributos durante la creación de instancias.

public abstract class Animal {


protected int numeroPatas;

public Animal( int patas ) {


this.numeroPatas = patas;
}
}

public class Leon extends Animal {


public Leon( int patas ) {
super( patas );
}
}

El constructor de una subclase debe clasificar el constructor de su superclase


si la superclase tiene un constructor explícito. La subclase debe hacer
referencia a los constructores explícitos de la superclase; de lo contrario, los
atributos de la superclase no se inicializarían correctamente. Para acceder a
los atributos, métodos y constructores de la superclase, la subclase utiliza la
palabra clave super.

Las subclases pueden anular los métodos de su superclase, lo que significa


que una subclase puede proporcionar su propia implementación para el
método de una superclase heredada.

public abstract class Animal {


protected int numeroPatas;

public void caminar() {


System.out.println( "El Animal camina");
}
}
public class Leon extends Animal {
public void caminar() {
System.out.println("Prefiero dormir");
}
}

En el ejemplo anterior, la clase Leon anuló el método de caminar de la clase


Animal. Si se le pide que camine, el sistema nos diría que un objeto Leon
prefiere dormir.

Tipos de herencia

Java es capaz de admitir varios tipos diferentes de herencia. Los ejemplos


anteriores en la sección son herencia de implementación.

En Java, solo se permite la herencia de implementación única. Esto significa


que mientras una superclase puede tener múltiples subclases, una subclase
solo puede heredar de una única superclase.

Por ejemplo, la clase Animal podría ser una superclase de varias subclases:
una clase León, una clase Lobo o una clase Ciervo. Cada una de estas clases
puede tener comportamientos o características especializados, por lo que un
objeto León sabe cómo rugir, pero puede no saber cómo aullar, como un
objeto Lobo.

Las subclases también pueden ser una superclase para otra clase. La herencia
puede filtrarse a través de tantas clases como se desee.

La herencia permite la generalización de clases relacionadas en una única


superclase y aún permite que las subclases conserven el mismo conjunto de
atributos y comportamientos. Esto elimina la redundancia en el código y
facilita la implementación de cambios.

Herencia de interfaz

Otros lenguajes como C++ admiten la herencia múltiple. Esto es cuando una
subclase puede tener dos o más superclases. Java aborda la restricción de la
herencia de implementación única al ofrecer herencia de interfaz, otra forma
de generalización. Para entender eso, primero es necesario explicar algunas
nociones de lenguaje de programación y diseño.

Una clase denota un tipo para sus objetos. El tipo indica lo que estos objetos
pueden hacer a través de métodos públicos. Al modelar un problema, puede
ser necesario expresar relaciones de subtipificación entre dos tipos. Por
ejemplo, las instancias de una clase Leon son objetos clasificados como Leon,
que pueden realizar comportamientos de león especializados. Un tipo Leon
también puede ser un subtipo de un tipo Animal. Por lo tanto, un objeto Leon
es de tipo León y de tipo Animal: un objeto Leon se comporta como un león y
como un animal. Por lo tanto, un león “es” un animal.

En Java, la herencia de implementación con la palabra clave extends se usa a


menudo para la creación de subtipos. Entonces, si una subclase extiende una
superclase, se comportará como la superclase y como su propia clase. La
subclase hereda los “detalles de implementación” de la superclase.

Una interfaz Java también denota un tipo, pero una interfaz solo declara
firmas de métodos, sin constructores, atributos o cuerpos de métodos.
Especifica los comportamientos esperados en las firmas del método, pero no
proporciona ningún detalle de implementación.

También se puede utilizar una interfaz Java para subtipificar. Si una clase
implementa una interfaz, entonces la clase no solo se comporta como sí
misma, sino que también se espera que se comporte de acuerdo con las firmas
de métodos enumeradas en la interfaz. La clase debe proporcionar los detalles
del cuerpo del método sobre lo que significa implementar la interfaz. Una
interfaz es como un contrato que debe cumplirse mediante la implementación
de clases.

En la herencia de implementación, existe consistencia entre el tipo de


superclase y el tipo de subclase. Un objeto de subclase se puede usar en
cualquier parte de su programa donde esté tratando con el tipo de superclase.
De manera similar, en la herencia de interfaz, existe coherencia entre el tipo
de interfaz y el tipo de clase de implementación.

En Java, la palabra clave interface se utiliza para indicar que se está definiendo
una. La letra "l" a veces se coloca antes de un nombre real para indicar una
interfaz.

public interface IAnimal {


public void mover();
public void hablar();
public void comer();
}

La interfaz muestra que un animal tiene los comportamientos de moverse,


hablar y comer, pero estos comportamientos no se implementan aquí y no
hay una descripción de cómo se realizan estos comportamientos. La interfaz
tampoco encapsula ningún atributo de la superclase; esto se debe a que los
atributos no son comportamientos.
Nota:
La última versión de Java permite implementaciones predeterminadas de
los métodos en las interfaces.

Para utilizar una interfaz, debe declarar que va a cumplir la declaración tal
como se describe en la interfaz. La palabra clave en Java para esta acción es
implements.

public class Lion implements IAnimal {


/* Los atributos del león se pueden ir aquí */

public void mover() { … }


public void hablar() { … }
public void comer() { … }
}

En este ejemplo, la clase Leon ha declarado que implementará o describirá


los comportamientos que se encuentran en la interfaz; en este caso, los
métodos mover, hablar y comer. Debe tener todas las firmas de métodos
explícitamente declaradas e implementadas en la clase.

«interface» Nombre Interface

Una interfaz Java puede describir el comportamiento común esperado de


varias clases, sin implementar directamente ese comportamiento. Una
interfaz puede ser implementada por múltiples clases, con cada clase
implementadora definiendo su propia versión apropiada del
comportamiento. Además, una clase puede implementar múltiples interfaces
diferentes.

Las interfaces se pueden dibujar de manera similar a las clases en los


diagramas UML. Las interfaces se indican explícitamente usando << >>, o
comillas francesas, para rodear la palabra «interface».

La interacción entre una interfaz y una clase que implementa la interfaz se


indica mediante una flecha punteada. La clase implementadora toca el
extremo de la cola de la flecha y la interfaz toca la punta de la flecha. La forma
convencional de dibujar interfaces en sus diagramas de clases UML es tener
la flecha apuntando hacia arriba, de modo que la interfaz siempre esté en la
parte superior y las clases que las implementan estén en la parte inferior.

Las interfaces tienen varias ventajas. Comprender cuáles son estas le ayudará
a determinar si debe usar interfaces o herencia al diseñar un sistema.

Al igual que las clases abstractas, que son clases de las que no se pueden crear
instancias, las interfaces son un medio en el que puede lograr el polimorfismo.
En lenguajes orientados a objetos, el polimorfismo es cuando dos clases
tienen la misma descripción de un comportamiento, pero las
implementaciones de ese comportamiento pueden ser diferentes. Un
ejemplo de esto podría ser cómo “hablan” los animales. Un león puede rugir,
pero un lobo aúlla. Ambos animales pueden hablar, pero la implementación
del comportamiento es diferente.

Esto se puede demostrar a través del código, como se muestra a continuación:

public class Leon implements IAnimal {


public void hablar() {
System.out.println( "Rugido!" );
}
}
public class Lobo implements IAnimal {
public void hablar() {
System.out.println( "Aullido!" );
}
}

Las interfaces pueden heredar de otras interfaces, pero las interfaces no


deben extenderse si simplemente está intentando crear una interfaz más
grande. La interfaz A solo debe heredar de la interfaz B si los
comportamientos en la interfaz A se pueden usar completamente como
sustitución de la interfaz B.

Examine el siguiente ejemplo para comprender mejor este concepto.

public interface IMovimientoVehicular {


public void movX();
public void movY();
}

En este ejemplo, un vehículo solo puede viajar a lo largo del eje x o del eje y.
Pero, ¿y si queremos que un tipo de vehículo diferente, como un avión o un
submarino, también pueda moverse a lo largo de un tercer eje? Para evitar
añadir un comportamiento extra a la interfaz, para no afectar a los vehículos
que solo pueden moverse sobre dos ejes, creamos una segunda interfaz que
hereda de la primera.

public interface IVehicleMovement3D extends


IMovimientoVehicular {
public void movZ();
}

Otra ventaja de las interfaces se relaciona con la herencia de múltiples


implementaciones. Esto se debe a que heredar de dos o más superclases
puede causar ambigüedad en los datos; si una subclase hereda de dos o más
superclases que tienen atributos con el mismo nombre o comportamientos
con la misma firma de método, entonces no es posible distinguirlos. Como
Java no puede decir a cuál se hace referencia, no permite la herencia múltiple
para evitar la ambigüedad de los datos.

Las interfaces, sin embargo, no tienen este problema. En Java, una clase puede
implementar tantas interfaces como desee. Esto se debe a que las interfaces
son solo declaraciones y no imponen una forma específica de completar estas
declaraciones, por lo que las firmas de métodos superpuestas no son un
problema. Es aceptable una implementación única para múltiples interfaces
con declaraciones superpuestas. No hay ambigüedad, ya que una clase solo
puede tener una definición de un método específico, y es la misma
implementación sin importar qué interfaz. Java evita la ambigüedad de datos
de esta manera.

Las clases pueden implementar una o más interfaces a la vez, lo que permite
varios tipos. Las interfaces le permiten describir comportamientos sin
necesidad de implementarlos, lo que permite reutilizar esas abstracciones. Las
interfaces permiten la creación de programas con código reutilizable y
flexible. Sin embargo, es importante recordar que no debe generalizar todas
las declaraciones de comportamiento en interfaces. Las interfaces satisfacen
una necesidad específica: proporcionar una forma para que las clases
relacionadas funcionen con consistencia.

También podría gustarte