Programación Dirigida a Objetos . MATERIAL COMPLEMENTARIO Tema 5.- Herencia 1. 2. Herencia múltiple en C++: ambigüedad de nombres.........................................................2 Herencia en SMALLTALK ...............................................................................................4 2.1. Determinación de los métodos a ejecutar: self y super..............................................4 2.2. Herencia de métodos y variables de instancia ...........................................................5 2.3. Herencia de métodos de clase ....................................................................................6 3. Herencia en JAVA ...........................................................................................................12 3.1. Herencia de interfaces..............................................................................................12 1 Programación Dirigida a Objetos . MATERIAL COMPLEMENTARIO 1. Herencia múltiple en C++: ambigüedad de nombres Una clase hereda métodos y atributos de más de una clase. class Mamifero { public: int tiempo_incubacion; char* habitat; void comer(char*); } class Oviparo { public: int tamaño_huevos; int habitat; void comer(char*); } class Hominido: public Mamifero { public: int altura; } class Ornitorrinco: public Mamifero, public Oviparo { void comer (char* c) { // this.comer(c) es ambigüo y no puede escribirse aquí this.Mamifero::comer(c) //deshace la ambigüedad } Ambigüedad o conflictos de nombres en herencia de métodos: Redefinición del método ambigüo, referenciando al que se desea heredar. 2 Programación Dirigida a Objetos . MATERIAL COMPLEMENTARIO class A { public: void virtual mostrar (int i) {.......} void virtual ver (int p) {....} } class B { public: void virtual mostrar (float f) {.......} void virtual ver (int n) {....} } class C: public B, public A { public: void virtual mostrar (int i) {A::mostrar(i);} void virtual mostrar (float f) {B::mostrar(f);} void virtual verA (int p) {A::ver(p);} void virtual verB (int p) {B::ver(p);} } main() { C c; c.mostrar(4); c.mostrar(2.8); } 3 Programación Dirigida a Objetos . MATERIAL COMPLEMENTARIO 2. Herencia en SMALLTALK 2.1. Determinación de los métodos a ejecutar: self y super. Clase Uno prueba ^1 resultado1 ^ self prueba Clase Dos hereda de Uno prueba ^2 Clase Tres hereda de Dos resultado2 ^ self resultado1 resultado3 ^ super prueba Clase Cuatro hereda de Tres prueba ^4 ejemplo1 := Uno new. ejemplo2 := Dos new. ejemplo3:= Tres new. ejemplo4 .= Cuatro new. ejemplo1 prueba. ejemplo1 resultado1 ejemplo2 prueba ejemplo2 resultado1 ejemplo3 prueba ejemplo4 resultado1 ejemplo3 resultado2 ejemplo4 resultado2 ejemplo3 resultado3 ejemplo4 resultado3 4 Programación Dirigida a Objetos . MATERIAL COMPLEMENTARIO 2.2. Herencia de métodos y variables de instancia La responsabilidad de los métodos de instancia es de la clase, es decir, cuando un objeto recibe un mensaje será su clase quien los ejecute. Por ejemplo, #(1 2 3) at:1 put:2 es un mensaje a un objeto de la clase Array. Cuando el objeto recibe el mensaje transfiere la responsabilidad de su ejecución a la clase. La figura 1 muestra la composición de la clase Carta (el objeto Carta). La clase contiene un diccionario con los métodos de instancia válidos para esa clase. Contiene, además, los nombres de las variables de instancia con nombre. Figura 1.- Composición de la clase Carta y un objeto de la clase Carta. Cada ventana indica en su título la clase a la que pertenece el objeto que se inspecciona. 5 Programación Dirigida a Objetos . MATERIAL COMPLEMENTARIO La herencia de métodos y variables de instancia sigue la jerarquía de clases típica de Smalltalk. Este es un resumen muy simplificado de ella. Object Behavior Class Metaclass Collection Set Dictionary SequenceableCollection ArrayedCollection Array List OrderedCollection CharacterArray String ByteString Magnitude Number Integer Fraction Character Time 2.3. Herencia de métodos de clase En las primeras versiones de Smalltalk todas las clases eran objetos (instancias) de una única clase, la clase Metaclass. Algo similar ocurre en JAVA. A nivel práctico esto implicaba que la clase Metaclass era responsable de responder al método new para crear objetos. De este modo era imposible redefinir el método new para cada clase. A partir de Smalltalk-80 se amplia el concepto de metaclase de forma que cada clase tenga la suya propia. Los métodos de clase, de los cuales el más típico es el método new, son entonces responsabilidad de la metaclase, es decir, la clase de la cual es instancia una clase. Cada clase tiene su correspondiente metaclase que se denomina con el nombre de la clase seguido del mensaje class . Por ejemplo, la metaclase de Object es Object class. La metaclase permite tratar las clases como objetos. Al tener información sobre la definición de una clase, la metaclase implementa y ejecuta los métodos de clase, en particular los de creación de objetos. Por ejemplo, el método palo:numero: de la clase Carta (figura 1) permite crear objetos de esta clase, en la figura 2 puede observarse que este método está contenido en el diccionario de métodos de Carta class, la metaclase de carta. 6 Programación Dirigida a Objetos . MATERIAL COMPLEMENTARIO Figura 2.- La metaclase de Carta (Carta class) y la clase Metaclass Cada metaclase tiene una única instancia, su clase. A su vez todas las metaclases son objetos de la clase Metaclass. Metaclass es objeto (instancia) de Metaclass class. Puesto que Metaclass class es una metaclase será instancia de Metaclass. En la siguiente figura podemos ver las relaciones instancia_de que se establecen. Metaclass Carta class Carta 7 de bastos Metaclass class Cada metaclase se responsabiliza de los métodos de clase de su clase. De forma que cuando se crea una clase, se crea primero su metaclase y se almacena en ella la definición de la clase. En la figura que sigue se observa de quién es responsabilidad cada método 7 Programación Dirigida a Objetos . MATERIAL COMPLEMENTARIO Carta class new palo:numero: Carta palo numero new palo:numero: palo palo: ‘bastos’ numero: 7 Pero a su vez, los métodos de clase, al igual que los de instancia, también se heredan. Es decir, igual que existe una jerarquía de herencia de clases, existe un jerarquía de herencia de metaclases. Es por esta jerarquía de metaclases por la que se heredan los métodos de clase. Metaclass Object class Object Carta class Carta 7 de bastos Metaclass class Pero, ¿de quién hereda Object class, Metaclass y Metaclass class? La cosa se complica. Aparecen dos nuevas clases: Behavior y Class. Todas las metaclases heredan de Class, que a su vez hereda de Behavior. Metaclass también es subclase de Behavior. De este modo los métodos de instancia definidos en Object se utilizan tanto para manipular los objetos de una clase como las clases y la metaclases. La consecuencias de esta estructura es que los métodos de instancia de Class se transforman en métodos de clase del resto de la jerarquía. Class sería un buen lugar para contener el método new como método de intancia, pero si fuera así no se podría utilizar new para crear clases y metaclases. New se define como método de instancia en Behavior y de este modo sirve tanto para crear objetos de una clase normal, al ser heredado por la vía de Class como método de clase, como para crear clases y metaclases. 8 Programación Dirigida a Objetos . MATERIAL COMPLEMENTARIO Object Behavior Metaclass Carta Class Object class Carta class Behavior class Metaclass class Class class Veámoslo de forma abstracta (no sucede exactamente de este modo). Supongamos que queremos crear la clase Bolígrafo. Habría que crear primero su metaclase: Metaclass new name:Boligrafo. New es en este caso un método de clase de Metaclass, luego debe ser ejecutado por Metaclass class. Como Metaclass class hereda de Behavior se crea la correspondiente metaclase. Name es un método de instancia de Metaclass, luego será Metaclass el que lo resuelva. Una vez creada la metaclase ya podemos crear la clase: (Bolígrafo class) new Este new va dirigido a una metaclase luego sería un método de instancia de Metaclass y sería Metaclass la responsable de él. De nuevo se puede ejecutar el new de Behavior o se puede redefinir en Metaclass, en este caso se redefine en Metaclass para que solo se pueda tener una clase para cada metaclase. Ya creada la clase se le pueden mandar mensajes para definir las variables y los métodos: Bolígrafo addInstVarName: ‘tinta’ El método addInstVarName: añade una variable de instancia (ver figura 1). Se trata de un método de clase, pero como en realidad manipula la clase como un objeto cualquiera está implementado en Class y es heredado por Bolígrafo class. La finalidad de todo este lío es: - Poder tratar las clases como objetos: Class hereda indirectamente de Object, luego los mensajes que entiende un objeto, que determinan su comportamiento y modifican su estado, también son válidos para una clase. - Poder heredar los métodos de creación de instancias: El método new, para crear instancias, está implementado en Behavior. Como Class y Metaclass heredan de 9 Programación Dirigida a Objetos . MATERIAL COMPLEMENTARIO Behavior, heredan este método y pueden crearse clases y metaclases. Todas las metaclases heredan de Class, y entre ellas está la metaclase Object class. Object class también hereda el método new, por lo cual también puede crearse cualquier objeto (recordemos que son las metaclases las que responden al mensaje new y son las que en realidad crean objetos) 10 Programación Dirigida a Objetos . MATERIAL COMPLEMENTARIO 11 Programación Dirigida a Objetos . MATERIAL COMPLEMENTARIO 3. Herencia en JAVA 3.1. Herencia de interfaces Una interface es una forma de declarar un tipo, de tal forma que éste conste de constantes y signaturas de métodos sin implementar. La interface define un tipo que puede ser usado para declarar variables. Una interface podrá ser implementada por clases diferentes que pueden o no tener relación de herencia entre sí. La interface puede considerarse similar a una clase abstracta pero se diferencian en lo siguiente: - La interfaz carece de implementación. En general una clase abstracta posee métodos abstractos, que deberán ser implementados por las subclases, y métodos implementados en ella. - Una interfaz puede heredar de más de una interfaz. - Las subclases de una clase abstracta tendrán entre sí alguna relación de generalización/especialización. Sin embargo, las clases que implementan una interfaz pueden no tener ninguna relación de herencia. Supongamos el siguiente problema: los datos de los clientes de una empresa se encuentran en diferentes bases de datos con diferentes estructuras. Se desea unificar el servicio de envío de correo electrónico a los clientes independientemente de la base de datos. Class Cliente String nombre String apellidos String email Enviar (String mensaje) Class ListaDeClientes Cliente [] clientes Abstract ObtenerTodosLosClientes AñadirCliente (Cliente c) OrdenarClientes EnviarATodos (String mensaje) {aCadaCliente.enviar (mensaje)} AñadirListaDeClientes (ListaDeClientes lista) Class ListaDeClientesOracle extends ListaDeClientes ObtenerTodosLosClientes {para cada cliente obtenido de la BD crear unCliente} Class GestorCorreoClientes ListaDeClientes listaGlobalDeClientes ListaDeClientesOracle listaOracle String mensaje EnviarATodos {listaOracle.obtenerTodosLosClientes; listaGlobalDeClientes.añadirListaDeClientes (listaOracle); 12 Programación Dirigida a Objetos . MATERIAL COMPLEMENTARIO listaGlobalDeClientes.ordenarClientes(); listaGlobalDeClientes.enviarATodos (mensaje)} Supongamos ahora que los Clientes a los que hay que enviar los mensajes son difícilmente unificables en una clase y que, además, para cada tipo de clientes se utiliza un programa de correo y un protocolo diferente de envío. En este caso recurrimos a una interface. Interface Cliente Enviar (String mensaje) Class Empresa implements Cliente String nombre String director String email Enviar (String mensaje) Class Individuo implements Cliente String apellidos String nombre String email Enviar (String mensaje) El método obtenerTodosLosClientes de ListaDeClientes debería también modificarse para crear una u otra clase de clientes. Otra solución sería crear una subclase de ListaDeClientes por cada tipo de cliente. Las interfaces pueden ‘heredarse’ y además de forma múltiple. Interface Empresario Regalar (String mensaje) Interface Persona Felicitar (String mensaje) Interface ClienteEmpresario extends Cliente, Empresario Interface ClientePersona extends Cliente, Persona Ahora podríamos hacer que las clase Empresa implemente ClienteEmpresario y la clase Indiciduo implemente ClientePersona. Evidentemente habría que implementar las operaciones pertinentes en cada clase. No existe la herencia múltiple en Java, aunque una clase puede heredar de otra e implementar al mismo tiempo los métodos de una Interface. interface Alimentos { void comer(String c); void beber(String b); } interface Ejercicios { void correr(int k); } class Dieta implements Alimentos { // uso de una interface 13 Programación Dirigida a Objetos . MATERIAL COMPLEMENTARIO private String comida; void comer(String c) { comida= c; }; void beber(String b) { System.out.println(b);} } class Dieta implements Alimentos, Ejercicios { //uso de varias interfaces: simulación de herencia múltiple private String comida; public int totalCorrido = 40; void comer(String c) { comida= c; }; void beber(String b) { System.out.println(b);}; void correr(int k) { return ( totalCorrido+ k) }; } En realidad, hay dos formas de “simular” en Java la herencia múltiple y podemos verla con el ejemplo del Ornitorrinco 1) Se toman métodos de varias interfaces interface Mamifero {...} interface Oviparo {...} class Ornitorrinco implements Mamifero, Oviparo { ...} 2) Se hereda de una clase y se toman métodos de una o más interfaces interface Mamifero {...} class Oviparo {...} class Ornitorrinco extends Oviparo implements Mamifero {...} En realidad no se trata de herencia múltiple puesto que las interfaces no permiten heredar ninguna implementación, pero al tener que implementar la interface Mamífero, la clase Ornitorrinco se puede comportar como un Mamífero allí donde se hayan declarado variables de tipo Mamífero. La interfaces dan una mayor potencia al polimorfismo al permitir que clases que no tienen ninguna relación de herencia puedan actuar de modo polimórfico. Las interfaces tienen una última ventaja de diseño: se puede proponer una interfaz como el protocolo mínimo que deben cumplir las clases para poder interactuar. Esto permite anticipar el comportamiento de clases que aún no han sido definidas. Por ejemplo, en JAVA Collection es una interfaz de modo que cualquier clase que la implemente se podrá comportar como una colección. 14