Download 4.4.Herencia.y.Polimorfismo.IPOO.2016

Document related concepts
no text concepts found
Transcript
Sonia Rueda
Herencia y Polimorfismo
Departamento de Ciencias e Ingeniería
de la Computación
UNIVERSIDAD NACIONAL DEL SUR
2016
IPOO 2 cuatrimestre 2016
Introducción a la
Programación
Orientada a Objetos
Paradigmas de Programación
El desarrollo de un sistema de software es un proceso
de abstracción a partir del cual se construye un
modelo de la solución de un problema.
En los problemas de mediana y gran escala la
construcción del modelo requiere creatividad, pero
también de un paradigma que guíe, oriente y
sistematice cada etapa del proceso.
Un paradigma brinda un principio, una metodología y
un conjunto de herramientas que favorecen la
aplicación del la metodología.
Programación Orientada a Objetos
El principio fundamental de la programación
orientada a objetos es desarrollar software en base
a las entidades de un modelo.
Una metodología orientada a objetos brinda
pautas para cada etapa del proceso de desarrollo
de software.
La estrategia sigue siendo dividir el problema en
subproblemas para reducir la complejidad. La
metodología nos indica cómo dividir.
Las principales herramientas son el lenguaje de
modelado y el lenguaje de programación.
Programación Orientada a Objetos
Desarrollo de requerimientos
Programación Orientada a Objetos
Diseño
Internado
Internación
Paciente
Control
SignosVitales
PresionArterial
Médico
Residente
Jefe de Servicio
El diseño puede incluir varios
diagramas de clases. Si hay
clases compartidas los
diagramas tienen que ser
consistentes.
Programación Orientada a Objetos
Diseño
Empleado
Técnico
Médico
Residente
Administrativo
Asistencial
Enfermero
Jefe de Servicio
Programación Orientada a Objetos
Implementación
APLICACIÓN
class Paciente
class Internado
class Empleado
class Asistencial
class Medico
class Residente
class JefeServicio
class SignosVitales
class Internación
class PresionArterial
class Control
Con frecuencia un programador implementa solo uno de los
diagramas de clase o incluso parte de un diagrama de
clases.
Programación Orientada a Objetos
Verificación
Servicios
Clases
Integración entre clases
Programación Orientada a Objetos
Modelo computacional
Internación
Internado
Médico
SignosVitales
Control
El modelo computacional de la programación orientada a
objetos es un mundo poblado de objetos comunicándose a
través de mensajes. Cada objeto que recibe un mensaje
ejecuta un servicio determinado por su clase.
Programación Orientada a Objetos
El objetivo de la programación orientada a objetos
es favorecer la calidad y la productividad.
La POO mejora la calidad y la productividad a
través de los conceptos de:
•
abstracción de datos y encapsulamiento
•
herencia y polimorfismo
Calidad de Software
La calidad de un producto de software puede
definirse como su capacidad para satisfacer los
requisitos establecidos durante el desarrollo de
requerimientos.
La calidad puede medirse de acuerdo a distintos
factores.
Algunos de estos factores son percibidos por el
usuario o cliente. Otros factores son transparentes
para el usuario o cliente, aunque por supuesto lo
afectan indirectamente.
Calidad de Software
Correctitud
Un producto de software correcto actúa de acuerdo a
los requerimientos especificados.
Eficiencia
Un producto de software es eficiente si tiene una baja
demanda de recursos de hardware, en particular
tiempo de CPU, espacio de memoria y ancho de
banda.
Portabilidad
Un producto de software es portable si puede
ejecutarse sobre diferentes plataforma de hardware y
de software.
Calidad de Software
Simplicidad
Un producto de software es simple si es fácil de usar, su
interfaz es amigable y no requiere demasiado
entrenamiento ni capacitación por parte del usuario.
Robustez
Un producto de software es robusto se reacciona
adecuadamente aun en circunstancias no
especificadas en los requerimientos.
Legibilidad
La legibilidad está fuertemente ligada a modularización
y la estructura del código. La legibilidad impacta en la
reusabilidad y la extensibilidad.
Productividad de Software
Extensibilidad
Un producto de software es extensible si es fácil
adaptarlo a cambios en la especificación de
requerimientos. El primer requerimiento para la
extensibilidad es la legibilidad.
Reusabilidad
Un módulo de software es reusable si puede
utilizarse para la construcción de diferentes
aplicaciones.
La herencia y el polimorfismo favorecen la
productividad.
Herencia
La herencia es un mecanismo que permite
organizar una colección de clases a través de una
relación de generalización-especialización.
Consideremos dos o más clases que comparten
algunos atributos y servicios y difieren en otros. Es
posible definir una clase base, con los atributos y
servicios compartidos y luego extender esa clase
base en dos o más clases derivadas.
La definición de las clases derivadas incluye solo los
atributos y comportamiento específicos, que
distinguen a los objetos de la clase derivada de las
instancias de la clase general.
Herencia
La herencia simple exige que el proceso de
clasificación se realice de manera tal que cada
clase derivada corresponda a una única clase base.
Gráficamente la estructura de clases forma un
árbol que describe la jerarquía de herencia.
En cada rama del árbol las clases superiores son
los ancestros de las clases inferiores, que son sus
descendientes.
La herencia múltiple permite que una clase
derivada pueda heredar de dos o más clases
generales. Es una alternativa poderosa pero más
compleja.
Herencia
Cuando la herencia es simple el árbol puede
construirse desde la raíz hacia las hojas o en
sentido inverso.
Es decir, el proceso de clasificación puede hacerse
partiendo de una clase muy general y
descomponiéndola en otras más específicas
identificando las diferencias entre los objetos.
El proceso continúa hasta alcanzar subclases
homogéneas, hablamos de especialización.
Herencia
Alternativamente es posible partir del dominio de
todas las instancias y agruparlas en clases según
sus similitudes, hablamos entonces de
generalización.
Estas clases serán a su vez agrupadas en otras de
mayor nivel hasta alcanzar una única clase muy
general.
Herencia
Empleado
iniciarTurno (t:Turno)
finTurno (t:Turno)
Asistencial
administrarFarmaco(p:Paciente,f:Farmaco)
controlarPaciente(p:Paciente):Control
masAntiguo(a:Asistencial):boolean
Médico
indicarInternacion(p:Paciente):Internacion
prescribirFarmaco(p:Paciente):Farmaco
controlarPaciente(p:Paciente):Control
Consideramos solo parte de una rama del árbol.
Herencia en Java
La herencia en Java es simple aunque puede
simularse herencia múltiple.
La clase derivada extiende a la clase base a través de
la palabra extends.
La clase derivada hereda atributos y métodos, pero
no los constructores. Los constructores de una clase
base pueden ser accedidos desde los constructores
de sus descendientes directos, usando la palabra
super.
Los métodos de la clase derivada pueden derogar a
los métodos de sus clases ancestros.
Herencia en Java
class Empleado{
…
public void iniciarTurno(Turno t){}
public void finTurno(Turno t){}
}
class Asistencial extends Empleado{
…
public void administrarFarmaco(Paciente p,Farmaco f){}
public Control controlarPaciente(Paciente p){}
public boolean masAntiguo(Asistencial a){}
}
class Medico extends Asistencial{
…
public Farmaco prescribirFarmaco(Paciente p){}
public Internacion
indicarInternacion (Paciente p){}
public Control controlarPaciente(Paciente p){} }
Herencia en Java
Paciente p = new Paciente(…);
Asistencial v1 = new Asistencial(…);
Medico v2 = new Medico(…);
Farmaco f = v2.prescribirFarmaco(p);
Las instancias de la clase Medico pueden recibir el
mensaje prescribirFarmaco con un objeto de
clase Paciente como parámetro.
Las instancias de la clase Asistencial no
tienen la capacidad de atender el mensaje
prescribirFarmaco.
Herencia en Java
Paciente p = new Paciente(…);
Asistencial v1 = new Asistencial(…);
Medico v2 = new Medico(…);
Farmaco f = v2.prescribirFarmaco(p);
v1.administrarFarmaco(p,f);
v2.administrarFarmaco(p,f);
La clase derivada Medico hereda los métodos
definidos en la clase base Asistencial.
Las instancias de la clase Medico son también
instancias de la clase Asistencial y de la clase
Empleado.
Modificadores en Java
El modificador final tiene significados
levemente distintos según se aplique a una
variable, a un método o a una clase.
Para una clase, final significa que la clase no
puede extenderse. Es, por tanto el punto final de la
rama de clases derivadas.
Para un método, final significa que no puede
redefinirse en una clase derivada.
Para una variable, final significa también que no
puede ser redefinido en una clase derivada, pero
además su valor no puede ser modificado.
Redefinición en Java
En Java mismo nombre puede utilizarse para definir
un método en la clase base y otro en la clase
derivada.
Si en la clase derivada se define un método con el
mismo nombre, número y tipo de parámetros que
un método definido en la clase base, el método de
la clase base queda derogado.
Decimos que la definición de la clase derivada
redefine al método de la clase base.
Los métodos derogados pueden accederse con la
palabra super.
Redefinición en Java
class Asistencial extends Empleado{
…
public Control controlarPaciente(Paciente p){}
}
class Medico extends Asistencial{
…
public Control controlarPaciente(Paciente p){}
}
Polimorfismo
El concepto de polimorfismo es central en la
programación orientada a objetos.
Polimorfismo significa muchas formas y en ciencias
de la computación en particular se refiere a “la
capacidad de asociar diferentes definiciones a un
mismo nombre, de modo que el contexto
determine cuál corresponde usar”.
En el contexto de la programación orientada a
objetos el polimorfismo está relacionado con
variables, asignaciones y métodos.
Polimorfismo en Java
Una variable polimórfica puede quedar asociada a
objetos de diferentes clases.
Una asignación polimórfica liga un objeto de una
clase a una variable declarada de otra clase
Un método polimórfico incluye una o más
variables polimórficas como parámetro.
Asignación polimórfica
Una asignación polimórfica liga un objeto de una
clase a una variable declarada de otra clase.
Empleado v0;
Asistencial v1 = new Asistencial(…);
Medico v2 = new Medico(…);
Son válidas las siguientes asignaciones polimórficas:
v0 = v1;
v0 = v2;
v1 = v2;
v0 = new Asistencial(…);
v0 = new Medico(…);
v1 = new Medico(…);
Tipo Estático y Dinámico
Dado que una variable puede estar asociada a
objetos de diferentes tipos, distinguiremos entre:
El tipo estático de una variable, es el tipo que
aparece en la declaración.
El tipo dinámico de una variable se determina en
ejecución y corresponde a la clase a la que
corresponde el objeto referenciado.
El tipo estático de una variable determina el
conjunto de tipos dinámicos a los que puede
quedar asociada y los mensajes que puede recibir.
Tipo Estático y Dinámico
Empleado
Asistencial
Médico
Tipo Estático
Tipos Dinámicos
Empleado
Empleado
Asistencial
Medico
Asistencial
Asistencial
Medico
Medico
Medico
Tipo Estático y Dinámico
Empleado v0
Asistencial
Medico v2 =
Empleado v3
= new Asistencial(…);
v1 = new Medico(…);
new Medico(…);
= new Medico(…);
Variable
Tipo
Estático
Tipo
Dinámico
v0
Empleado
Asistencial
v1
Asistencial
Medico
v2
Medico
Medico
v3
Empleado
Medico
Método polimórfico
El pasaje de parámetros puede involucrar una
asignación polimórfica:
Asistencial v1 = new Asistencial();
Medico v2 = new Medico();
El método definido en la clase Asistencial como:
public boolean masAntiguo(Asistencial e)
{ … }
Puede usarse con un argumento de clase Medico:
v1.masAntiguo (v2);
Ligadura dinámica de código
La ligadura dinámica de código es la vinculación en
ejecución de un mensaje con un método.
Polimorfismo, redefinición de métodos y ligadura
dinámica de código son conceptos fuertemente
ligados.
La posibilidad de que una variable pueda
referenciar a objetos de diferentes clases y de que
existan varias definiciones para una misma
signatura, brinda flexibilidad al lenguaje siempre
que además exista ligadura dinámica de código.
Ligadura dinámica de código
Paciente p = new Paciente(…);
Control c;
Asistencial v1 = new Asistencial(…);
Medico v2
= new Medico(…);
Asistencial v3 = new Medico(…);
c = v1.controlarPaciente(p);
Asistencial
c = v2.controlarPaciente(p);
Medico
c = v3.controlarPaciente(p);
Medico
El tipo dinámico determina la ligadura entre el
mensaje y el método.
Chequeo de Tipos en Java
El polimorfismo es un mecanismo que favorece la
reusabilidad pero debe restringirse para brindar
robustez
En Java el polimorfismo y la ligadura dinámica
quedan restringidos por el chequeo de tipos.
Los chequeos de tipos en compilación previenen
errores de tipo en ejecución.
El chequeo de tipos establece restricciones sobre:
•
las asignaciones polimórficas
•
los mensajes que un objeto puede recibir
Chequeo de tipos en Java
Asistencial v1
Medico v2
Asistencial v3
= new Asistencial(…);
= new Medico(…);
= new Medico(…);
v2 = v1;
Error
v2 = v3;
Error
v2 = new Asistencial(…);
Error
El tipo estático restringe las asignaciones
polimórficas.
Chequeo de tipos en Java
Paciente p = new Paciente(…);
Farmaco f;
Asistencial v1 = new Asistencial(…);
Medico v2
= new Medico(…);
Asistencial v3 = new Medico(…);
f = v1.prescribirFarmaco(p);
Error
f = v2.prescribirFarmaco(p);
f = v3.prescribirFarmaco(p);
Error
El tipo estático determina los mensajes que el objeto
puede recibir.
Chequeo de tipos en Java
El pasaje de parámetros puede involucrar una
asignación polimórfica:
Asistencial v1 = new Asistencial();
Empleado v2 = new Medico();
El método definido en la clase Asistencial como:
public boolean masAntiguo(Asistencial e)
{ … }
NO ES VÁLIDO:
v1.masAntiguo (v2);
Caso de Estudio: Gestión Residuos
En un sistema de control de residuos un terrero se
representa a través de una matriz de nxn
Cada elemento de la matriz pueden mantener un
contenedor.
Cada contenedor tiene una densidad y un volumen y
provoca un impacto sobre el medio ambiente que es
igual a la mitad de su tamaño multiplicado por la
densidad.
Un contenedor contaminante tiene un nivel de
toxicidad que incide en el impacto ambiental, el
impacto es el de cualquier contenedor multiplicado por
la toxicidad.
Caso de Estudio: Gestión Residuos
Caso de Estudio: Gestión Residuos
Contenedor
obtenerVolumen():real
obtenerDensidad():real
obtenerImpacto():real
Contaminante
obtenerToxicidad():real
obtenerImpacto():real
Terreno
colocar(c:Contenedor,f,c:entero)
retirar(f,c:entero):Contenedor
impactoTerreno():real
hayContenedor(f,c:entero):boolean
Caso de Estudio: Gestión Residuos
Contenedor
<<atributos de instancia>>
volumen:real
densidad:real
<<constructor>>
Contenedor(v:real,d:real)
<<consultas>>
obtenerVolumen():real
obtenerDensidad():real
obtenerImpacto():real
Contaminante
<<atributos de instancia>>
toxicidad:real
<<constructor>>
Contaminante(v,d,t:reall)
<<consultas>>
obtenerToxicidad():real
obtenerImpacto():real
Terreno
<<atributos de instancia>>
T [][] Contenedor
<<constructor>>
Terreno(n:entero)
<<comandos>>
colocar (c:Contenedor,f,c:entero)
retirar(f.c:entero):Contenedor
<<consultas>>
hayContenedor(f,c:entero):boolean
impactoTerreno():real
Caso de Estudio: Gestión Residuos
class Contenedor {
//atributos de instancia
protected float volumen;
protected float densidad;
//constructor
public Contenedor(float v,float d){
volumen = v; densidad = d;
}
//consultas
public float obtenerVolumen(){
return volumen;}
public float obtenerDensidad(){
return densidad;}
Caso de Estudio: Gestión Residuos
public float obtenerImpacto(){
return volumen*densidad/2;}
}
Caso de Estudio: Gestión Residuos
class Contaminante extends Contenedor {
//atributos de instancia
protected float toxicidad;
//constructor
public Contaminante (float v,
float d,float t){
super(v,d);
toxicidad = t;
}
//consultas
public float obtenerToxicidad(){
return toxicidad;}
Caso de Estudio: Gestión Residuos
public float obtenerImpacto(){
float i = super.obtenerImpacto();
return i*toxicidad; }
}
La consulta obtenerImpacto en la clase
Contaminante redefine y usa a la consulta
definida en la clase Contenedor.
Caso de Estudio: Gestión Residuos
class Contenedor {
public String toString(){
return volumen+” “+densidad;
}
}
class Contaminante extends Contenedor {
public String toString(){
return super.toString()+” “+toxicidad;
}
}
Caso de Estudio: Gestión Residuos
class Terreno{
//atributos de instancia
private Contenedor [][]T ;
//constructor
public Terreno (int n){
T = new Contenedor[n][n];
}
El arreglo T es una estructura de datos polimórfica,
los elementos pueden ser referencias a objetos de
la clase Contenedor o de la clase
Contaminante, que especializa a
Contenedor.
Caso de Estudio: Gestión Residuos
//comandos
public void colocar(Contenedor con,int f, int c){
//Requiere la posición válida
T[f][c] = con;
}
public Contenedor retirar(int f, int c){
//Requiere la posición válida
Contenedor con;
con = T[f][c];
T[f][c] = null;
return con;
}
El comando colocar es un método polimórfico,
recibe como parámetro a una variable polimórfica.
Caso de Estudio: Gestión Residuos
//consultas
public boolean hayContenedor(int f,int c){
return T[f][c] != null;
}
Caso de Estudio: Gestión Residuos
Contenedor
<<atributos de instancia>>
volumen:real
densidad:real
<<constructor>>
Contenedor(v:real,d:real)
<<consultas>>
obtenerVolumen():real
obtenerDensidad():real
obtenerImpacto():real
Contaminante
<<atributos de instancia>>
toxicidad:real
<<constructor>>
Contaminante(v,d,t:reall)
<<consultas>>
obtenerToxicidad():real
obtenerImpacto():real
Terreno
<<atributos de instancia>>
T [][] Contenedor
<<constructor>>
Terreno(n:entero)
<<comandos>>
colocar (c:Contenedor,f,c:entero)
retirar(f.c:entero):Contenedor
<<consultas>>
hayContenedor(f,c:entero):boolean
impactoTerreno():real
Caso de Estudio: Gestión Residuos
public float impactoTerreno(){
float impacto=0;
for (int i = 0; i< T.length;i++)
for (int j = 0; j< T.length;j++)
if (T[i][j] != null)
impacto += T[i][j].obtenerImpacto() ;
return impacto;
}
La ligadura entre el mensaje y el método
obtenerImpacto es dinámica, se resuelve en
ejecución y depende de la clase del objeto.
Caso de Estudio: Gestión Residuos
Terreno
<<atributos de instancia>>
T [][] Contenedor
<<constructor>>
Terreno(n:entero)
<<comandos>>
colocar (c:Contenedor,f,c:entero)
retirar(f,c:entero):Contenedor
<<consultas>>
hayContenedor(f,c:entero):boolean
impactoTerreno():real
mayorToxicidad():real
Computa la mayor toxicidad entre los
contenedores contaminantes. Si no se
registra toxicidad retorna 0.
Caso de Estudio: Gestión Residuos
//consultas
public float mayorToxicidad(){
float mayor=0;
for (int i = 0; i< T.length;i++)
for (int j = 0; j< T.length;j++)
if (T[i][j] != null &&
T[i][j].obtenerToxicidad() > mayor)
mayor = T[i][j].obtenerToxicidad();
return mayor;
}
El compilador reporta un error porque no puede
asegurar que T[i][j] pueda recibir el mensaje
obtenerToxicidad.
Caso de Estudio: Gestión Residuos
//consultas
public float mayorToxicidad(){
float mayor=0; Contaminante c;
for (int i = 0; i< T.length;i++)
for (int j = 0; j< T.length;j++)
if (T[i][j] != null)
if (T[i][j] instanceof Contaminante){
c = (Contaminante) T[i][j];
if (c.obtenerToxicidad() > mayor)
mayor = c.obtenerToxicidad();
}
return mayor;
}
La solución es correcta pero no respeta las
recomendaciones de la programación orientada a
objetos.
Caso de Estudio: Gestión Residuos
if (T[i][j] instanceof Contaminante)
Computa true si T[i][j] mantiene una
referencia a un objeto de clase Contaminante.
c = (Contaminante) T[i][j];
La asignación es válida porque el casting relaja el
control del compilador.
mayor = c.obtenerToxicidad();
El mensaje es válido porque el tipo estático de c es
Contaminante.
Caso de Estudio: Gestión Residuos
//consultas
public float mayorToxicidad(){
float mayor=0; Contaminante c;
for (int i = 0; i< T.length;i++)
for (int j = 0; j< T.length;j++)
if (T[i][j] != null){
c = (Contaminante) T[i][j];
if (c.obtenerToxicidad() > mayor)
mayor = c.obtenerToxicidad();
}
return mayor;
}
El compilador no reporta error pero la ejecución
terminará anormalmente, si la tabla contiene
referencias a contenedores que no son contaminantes.
Caso de Estudio: Gestión Residuos
Terreno
<<atributos de instancia>>
T [][] Contenedor
<<constructor>>
Terreno(n:entero)
<<comandos>>
colocar (c:Contenedor,f,c:entero)
retirar(f,c:entero):Contenedor
<<consultas>>
hayContenedor(f,c:entero):boolean
impactoTerreno():real
mayorToxicidad():real
filasMellizas(otro:Terreno):boolean
Computa true sí y solo sí el terreno
que recibe el mensaje y el terreno otro
tienen alguna fila con contenedores
dispuestos en las mismas posiciones o
ningún contenedor
Caso de Estudio: Gestión Residuos
La segunda fila hace que se verifique la propiedad
filasMellizas
Caso de Estudio: Gestión Residuos
La tercera fila hace que se verifique la propiedad
filasMellizas
Caso de Estudio: Gestión Residuos
No se verifica la propiedad
Caso de Estudio: Gestión Residuos
Algoritmo filasMellizas
para cada fila y mientras no se encuentren dos filas
mellizas
para cada columna y mientras no encuentre dos
celda tales que una es nula y la otra no
avanzar en la columna
Caso de Estudio: Gestión Residuos
Algoritmo filasMellizas
para cada fila y mientras no se encuentren dos filas
mellizas
para cada columna y mientras no encuentre dos
celda tales que una es nula y la otra no
avanzar en la columna
Comienza asumiendo que no hay filas mellizas y para
cuando encuentra dos filas melliza.
Caso de Estudio: Gestión Residuos
Algoritmo filasMellizas
para cada fila y mientras no se encuentren dos filas
mellizas
para cada columna y mientras no encuentre dos
celda tales que una es nula y la otra no
avanzar en la columna
Comienza asumiendo que las filas son mellizas y para
cuando decide que no lo son.
Caso de Estudio: Gestión Residuos
//consultas
public boolean filasMellizas(Terreno otro){
boolean mellizas=false;
for (int i = 0; i< T.length &&
!mellizas;i++){
mellizas=true;
for (int j = 0; j< T.length &&
mellizas;j++)
mellizas = (T[i][j] == null &&
!otro.hayContenedor(i,j))||
(T[i][j] != null &&
otro.hayContenedor(i,j));}
return mellizas;
}
Observemos que T es un arreglo de dos
dimensiones, la variable otro mantiene una
referencia a un objeto de clase Terreno.