Programación Orientada a Objetos IMPLEMENTACION LENGUAJES OO Objetos como Tipo de Datos Abstractos • Los Objetos combinan estados (campos de datos / variables de instancia / miembros de datos / slots) y comportamiento (operaciones / métodos / funciones miembro / funciones) • Desde fuera, los clientes solo pueden ver el comportamiento del objeto, desde adentro, los métodos proveen el comportamiento a través de modificaciones de estado y de interacción con top otros objetos. top pop push pop push 2 Interfase e Implementación • Principio de Parnas para módulos: – Se debe proveer al usuario con toda la información necesaria para utilizar el módulo correctamente y nada más. – Se debe de proveer al implementador con toda la información para completar el módulo y nada más. • Principio de Parnas para objetos: – La definición de una clase debe proveer al usuario de toda la información necesaria para manipular las instancias de la clase correctamente y nada más. – Un método debe tener acceso a toda la información necesaria para llevar a cabo sus responsabilidades y nada más. 3 Aprendiendo un Lenguaje OO (1) • Definición de Clase – es como una definición de tipo – especifica los datos y el comportamiento (métodos) a ser asociados con los datos – No crea un valor (objeto) – Pueden definirse valores por defecto para los datos. – La visibilidad de los datos y comportamientos puede ser especificado 4 Aprendiendo un Lenguaje OO (2) • Instanciación de Objetos – Es la creación de una nueva instancia de una clase. Ej.: un valor (objeto) – Se pueden definir valores iniciales para los campos de datos. [Existe confusión en algunos lenguajes con verificación fuerte de tipos donde la instanciación de objetos toma sintácticamente la forma de una declaración] • Mensajes – Tienen un receptor (el objeto al cual se envía el mensaje) – Tiene un selector de mensaje (algún nombre que indica que mensaje se esta enviando – Tiene argumentos. 5 Aprendiendo un Lenguaje OO (3) • Jerarquía de Clases – Son definidas cuando en la definición de una clase, una o màs clases padres son especificadas (herencia simple o multiple) • Herencia – Es la propiedad por la cual las instancias de una clase hija o subclase pueden acceder tanto a los datos como al comportamiento (métodos) asociados con una clase padre o superclase. • Unión de métodos o búsqueda de método – Es un mecanismo para determinar que método será usado en respuesta a un mensaje enviado 6 Ejemplo de la Carta 1 •Una carta pertenece a un palo, tiene un valor y un color •Una carta pude estar boca arriba o boca abajo y puede ser volteada de una posición a otra. •Una carta pude ser mostrada o retirada. •Una carta mágica es un tipo especial de carta que puede cambiar su valor 7 Definición de la Clase Carta Palo: {brillo, trebol, corazon, espada} Valor: integer BocaArriba: boolean Color: {rojo, negro} Voltear() <algún código> Dibujar(W:window;X,Y:position) <algún código> Borrar() <algún código> “define class” <nombre> <definición datos> <declaración métodos & definiciones> “define class” Carta ... ... 8 Definición de Clase: visibilidad, alcance, protección Carta Priv Fix Palo: {brillo, trebol, corazon, espada} Priv Fix Valor: integer Priv BocaArriba: boolean Pub Class NrdeCartasenJuego: integer Pub Pub Pub Pub define class” <nombre> <definición datos> <declaración métodos & definiciones> Voltear() Dibujar(W:window;X,Y:position) ... Borrar() Class Barajar() 9 Definición de Clase • Modificadores de Visibilidad para controlar la visibilidad y así la manipulación de los campos de datos y los métodos • Métodos para Setear y Obtener para controlar como los datos son accedidos y modificados • Campos de datos constantes o inmutables para garantizar que no ocurran cambios en ellos. • Campos de datos de clase (variables de clase) que son compartidos entre todas las instancias. • Interfaces e Implementación 10 Clases y Métodos en C++ (1) • C++ distingue archivos de interfase (extensión .h) y archivos de implementación (extensión .cpp) • El #include puede ser usado para "importar" descripciones de interfaz en los archivos de implementación • Las definiciones de clase se parecen a las definiciones de estructuras pero ambos, datos y métodos son permitidos. • Debido a que los métodos son tratados implemente como tipos especiales de campos, un método y un campo de datos no pueden tener el mismo nombre. • Los métodos con el mismo nombre que la clase son especiales: son los constructores y son usados para crear instancias de la clase. 11 Clases y Métodos en C++ (2) • La palabra reservada private precede aquellas porciones del código que pueden ser accedidas solo por los métodos de la clase en sí. • La palabra reservada public precede a aquellas porciones de código que pueden ser accedidas por los clientes de la clase. • Debido a que los usuarios de la clase están mayormente interesados en las partes públicas, estas deberían estar primero. • Las descripciones de los miembros privados son dadas para beneficio del compilador solamente. Los clientes no deberían verlas viola el principio de Parnas. 12 Clases y Métodos en C++ (3) • Un archivo de implementación de una clase de proveer las definiciones de los métodos descritos en el archivo de interfase (a menos que sea un método virtual puro) • Las funciones Inline se proveen para promover el uso de los principios de abstracción y encapsulación al mismo tiempo que se evita la sobrecarga de una llamada a procedimiento. • Las definiciones Inline ocurren también cuando el cuerpo de un método es definido directamente en la definición de clase; esto hace las definiciones de clase más difíciles de leer y algunos compiladores requieren que se listen los miembros privados de datos antes que la interfaz pública. 13 C++ Carta: Archivo de Interfaz class carta { public: enum palos {brillo, trebol, corazon, espada}; enum colores {rojo, negro}; carta(palos,int); //constructor colores color(); //acceso a atributos bool EstaBocaArriba(); int valor(); palos palo(); void Voltear(); //acciones void Dibujar(window &, int, int); void Borrar(window &, int, int); private: bool BocaArriba; int valordelValor; palos valordelPalo; }; 14 C++ Carta: Archivo de Implementación #include "carta.h" carta ::carta(palos sv ,int rv); { valordelPalo = sv; valordelValor = rv; BocaArriba = true; } inline int carta::valor() { return valordelValor; } colores carta::color() { if (palo()==corazon || palo()==brillo) return rojo; else return negro; } void carta::Voltear() { BocaArriba = ! BocaArriba } 15 C++ Carta: Archivo Interfaz (bis) class carta { private: bool BocaArriba; const int valordelValor; const palos valordelPalo; public: enum palos {brillo, trebol, corazon, espada}; enum colores {rojo, negro}; carta(palos sv , int rv): valordelPalo(sv), valordelValor (rv) { }; //constructor colores color(); //acceso atributos bool EstaBocaArriba() { return BocaArriba; }; int valor() { return valordelValor; }; palos palo() { return valordelPalo; }; ... 16 Java • Java no es un dialecto de C++, hay similaridades superficiales, pero las diferencias internas son substanciales. • Java no tiene punteros, referencia, estructuras, uniones, sentencias goto, definición de funciones o sobrecarga de operadores. • Java usa recolección de basura para manejo de memoria, introduce el concepto de interfaces, introduce los paquetes y la visibilidad de paquetes, soporta multihilo y manejo de excepciones. • Java es un lenguaje muy portable debido a que usa una máquina virtual para ejecutarse. 17 Clases y Métodos en Java • No hay preprocesador, variables globales o tipos de datos enumerados. Los valores simbólicos son creados por declaración e inicialización de un campo de datos con las palabras final y static. • La palabra reservada static usada en los campos de datos indica que solo una copia del campo de datos existe y es compartida por todas las instancias. En un método indica que este existe aún cuando no existan instancias de la clase. Un programa de Java necesita una clase con un método main static. • La implementación de los métodos debe ser proporcionada directamente en la definición de la clase. • Las palabras reservadas public y private son aplicadas individualmente a cada definición de variable o método. 18 Clase Carta: Java (1) class Carta { final static public int rojo = 0; final static public int negro = 1; final static public int espada = 0; final static public int corazón = 1; final static public int brillo = 2; final static public int trebol = 3; private boolean bocaArriba; private int valordePalo; private int valordeValor; // valores estáticos // campos de datos 19 Clase Carta: Java (2) Carta (int sv, int rv) // constructor { valordePalo = sv; valordeValor = rv; bocaArriba = true; } public boolean estaBocaArriba() { return bocaArriba; } // acceso atributos public int valor() { return valordeValor; }; public int palo() { return valordePalo; }; public int color() { if (palo() == corazon || palo() == brillo) //acciones return rojo; return negro; } public void voltear() { bocaArriba = ! bocaArriba; } public void dibujar( Graphics g, intx, int y) … }; //clase Carta 20 CLOS • Common Lisp Object System es un extensión orientada a objetos del lenguaje LISP • CLOS usa un acercamiento funcional al contrario del paso de mensajes para invocar operaciones específicas de las clases • CLOS soporta actualización automática de las instancias cuando cambia la definición de las clase y permite que las instancias se conviertan en instancias de una clase diferente. 21 CLOS Funciones Genéricas • Asuma que existen las clases racional y complejo y siguen las siguientes definiciones: (defgeneric suma (x y)) (defmethod suma (x y) (+ x y)) (defmethod suma ((x racional) (y racional)) (<cuerpo>)) (defmethod suma ((x complejo) (y complejo)) (<cuerpo>)) • El método ejecutado cuando se llama (suma A B) dependerá de los tipos de A y B, el método es escogido por un proceso que selecciona, ordena y combina los métodos aplicables. 22 Clases y Métodos en CLOS • El macro defclass define una clase con nombre y retorna un nuevo objeto clase • Una clase CLOS define slots que contienen las instancias de esa clase • Un slot tiene un nombre y puede contener un valor simple; un slot sin un valor es un slot nounido. • Ambos especificadores de slots y opciones de clase son usados para determinar las características de los slots y crear maneras de manipular el slot • Se puede añadir documentación a los slots y a las clases 23 Clases y Métodos en CLOS • El especificador :allocation permite distinguir entre slots locales y compartidos. Un slot es local cuando su valor es único para cada instancia individual. Un slot es compartido si es común para todas las instancias de una clase. • El especificador :type permite especificar un tipo para un slot • El especificador :documentation permite añadir documentación al slot 24 Clases y Métodos en CLOS • Los specificadores :reader, :writer y :accessor crean métodos especiales que permiten leer, escribir o ambos los valores de un slot. • El especificador :initarg introduce una palabra clave que puede ser usada mientras se instancia una clase para inicializar un slot. • Los especificadores :initform permite especificar un valor por defecto para un slot. • La opción de clase :documentation permite añadir documentación a una clase • La opción de clase :default-args permite especificar valores por defecto para los slots de una clase usando los initarg. 25 Clases y Métodos en CLOS • La macro defmethod define un método y devuelve un objeto método. • La clase en la que un método es definido se menciona explicitamente • Los métodos pueden ser calificados como :before, :after o :around • Cuando no existe una función genérica con el mismo nombre esta se genera automáticamente. – Los argumentos deben ser compatibles sino se produce un error – Cuando no contienen un objeto método con el mismo especializador y calificador un nuevo objeto método es agregado. – Cuando este contiene un objeto método con el mismo especializador y calificador, el método viejo es remplazado. 26 Clase Carta: CLOS (1) (defclass carta () ((bocaArriba :initarg :bocaArriba :accessor estaBocaArriba :allocation :instance :initform 'true) (palo :initarg :palo :reader palo :allocation :instance) (valor :initarg :valor :reader valor :allocation :instance) (dueno :allocation :class :reader dueno :initform 'vivi)) (:documentation “una clase de ejemplo")) 27 Clase Carta: CLOS(2) (defclass carta () ((bocaArriba :initarg :bocaArriba :accessor estaBocaArriba :allocation :instance :initform 'true) (palo :initarg :palo :reader palo :allocation :instance) (valor :initarg :valor :reader valor :allocation :instance) (:default-initargs :suit „espada :rank 1)) (defmethod voltear ((c carta)) (setf (estaBocaArriba c) (not (estaBocaArriba c)))) (defmethod color ((c carta)) (if (or (eq (palo c) „corazon) (eq (palo c) „brillo)) „rojo „negro)) 28 Instanciación e Inicialización “make instance” Carta brillo, 9, false (una Carta) palo: brillo valor: 9 bocaArriba: false color: rojo “make instance” Carta espada, 4, true (otra Carta) palo: espada valor: 4 bocaArriba: true color: negro “make-instance” <clase> <valores iniciales> 29 Paso de Mensajes (una Carta) palo: brillo Valor: 9 bocaArriba: false voltear() “send message” <objeto> <selector mensaje> < argumentos> true Carta Barajar() 30 Creación e Inicialización • • • • • • • • Alocación en Stack versus Heap Variables Automáticas versus Dinámicas Creación Implícita versus Explicita Recuperación de Memoria Manual versus Recolección de Basura Automática Punteros Explícitos versus Implícitos Creación Inmutable versus Asignación simple de instancias a variables o slots. Inicialización y valores por defecto; Constructores Recuperación de Memoria; Destructores 31 Paso de Mensajes • El paso de mensajes es el proceso dinámico de pedir a un objeto que realice una acción. • Los mensajes tienen un receptor (el objeto al cual se envía el mensaje), un selector del mensaje (el nombre del mensaje) y argumentos. • La búsqueda de método es un mecanismo para determinar el método a utilizar en respuesta a un mensaje recibido. • La diferencia entre lenguajes estáticos y dinámicos es importante para el paso de mensajes. 32 Creación e Inicialización en C++ • Existen variables automáticas y dinámicas • Una variable automática es asignada al espacio cuando se introduce el bloque que contiene la declaración. La declaración no necesita estar al comienzo del bloque. La instancia que se convierte en el valor de la variable es creada implícitamente y el espacio es reservado en la pila. El espacio es liberado una vez que el control sale del bloque. • Una variable dinámica implica el uso de manera explícita de puntero y una creación de instancia explícita a través del operador new. Se separa espacio en el heap y debe ser luego liberado a través del operador delete. 33 Creación e Inicialización en C++ • La inicialización en C++ se facilita a través del uso de constructores; estos son métodos que son invocados implícitamente cada vez que un objeto de una clase es creado ya sea implícita como explícitamente. • Los mecanismos de sobrecarga de operadores en C++ soporta el uso de múltiples constructores en una clase para permitir múltiples estilos de inicialización. • Hay soporte sintáctico para el uso de constructores en la declaración de variables automáticas y en el operador new. • Los constructores pueden pasar argumentos a constructores de objetos encapsulados. 34 Creación e Inicialización en C++ • Cuando el cuerpo del constructor es un conjunto simple de asignaciones a variables de instancia, este puede ser remplazado por cláusulas de inicialización en el encabezado de la función. • Los valores pueden ser declarados como inmutables a través de la palabra reservada const. Las variables de instancia que son declaradas como constantes deben ser inicializadas con una cláusula de inicialización porque no pueden ser blanco de una sentencia de asignación. • La limpieza de memoria en C++ esta facilitada a través del uso de destructores; estos métodos son llamados implícitamente cada vez que un objeto de la clase es borrado ya sea implícita como explícitamente. 35 Paso de Mensajes en C++ • Un método en C++ es llamado una función miembro; pasar un mensaje a un objeto es conocido como la invocación de una función miembro. • La sintaxis es similar a aquella usada para acceder miembros de datos con los argumentos en una lista. Se debe usar paréntesis así no haya argumentos. • La seudo-variable this esta asociada con un puntero a la clase receptora del mensaje; esta puede ser usada para enviar mensajes subsecuentes al receptor o para pasar el receptor como un argumento a otro mensaje. 36 Ejemplo de Carta: C++ carta lacarta(brillo, 9) carta * micarta micarta = new carta(corazon, 9) lacarta.dibujar(win1,10,10) if(lacarta.estaBocaArriba()) micarta->dibujar(win1,10,10) if(micarta->estaBocaArriba()) colores carta::color() { if (this->palo()==corazon || this->palo()==brillo) return rojo; else return negro; } void carta::haceralgo (int i) { hacerotracosa(this,i); } 37 Creación e Inicialización en Java • Java utiliza recolección de basura automática. El programador no tiene que tratar con el mantenimiento de memoria. • Todas las variables de algún tipo de objeto son inicialmente asignadas el valor null. • Los objetos son creados con el operador new; se alocaciona espacio en el heap, los punteros son siempre implícitos. • La sintaxis del operador new requiere el uso de paréntesis aún cuando los argumentos no son necesarios. • Los constructores en Java, no soportan cláusulas de inicialización. • Los constructores en Java pueden llamar a otros constructores en la misma clase usando la palabra this; esto permite factorizar el comportamiento común. 38 Creación e Inicialización en Java • El destructor en Java es representado por la función finalize. Esta función es llmada cada vez que se recupera la memoria a través del recolector de basura. • El operador new en Java puede tomar como argumento una cadena. Esto permite determinar en tiempo de ejecución el tipo del objeto que será alocacionado dado que la cadena puede ser construida con una expresión. • Las variables de instancia que no pueden ser reasignadas pueden ser creadas a través de la palabra reservada final. 39 Paso de Mensajes en Java • La única diferencia notable con C++ radica en el hecho de que las variables declaradas como un tipo de clase tienen los objetos como valor (el puntero esta implícito); esto es también el caso de la seudo-variable this carta micarta = new carta(corazon, 9) micarta.dibujar(win1,10,10) if(micarta.estaBocaArriba()) colores carta::color() { if (this.palo()==corazon || this.palo()==brillo) return rojo; else return negro; } 40 Creación e Inicialización en CLOS • CLOS usa un mecanismo de recolección de basura. • La función genérica make-instance es usada para crear e inicializar nuevas instancias de una clase. • El primer argumento para make-instance es el nombre del objeto clase, el resto de argumentos forman la lista de inicialización. • Los argumentos de inicialización que son proporcionados son combinados con las inicializaciones por defecto para los slots que no tienen. • Almacenamiento para las instancias se reserva en el heap, los slots se llenan con los valores y una instancia anónima del objeto se devuelve. 41 Creación e Inicialización en CLOS • La inicialización en CLOS es muy sofisticada: – El mismo nombrer initarg puede ocurrir en múltiples slots – Un slot puede tener múltiples nombre initarg – Dos nombres initarg que son sinónimos pueden ocurrir en la misma inicialización. – Se pueden escribir métodos para controlar explicitamente la inicialización. Initargs can be used to supply arguments to methods • La inicialización en CLOS no soo ocurre cuando se crea una nueva instancia sino que : – Cuando se reinicializa una instancia. – Cuando se actualiza una instanaic cuando su clase se actualiza – Cuando se actualiza una instancia para cambiar su clase a otra clase. 42 Paso de Mensajes en CLOS • Los métodos son definidos relativamente a una clase específica. Para cada conjunto de métodos con un nombre dado hay una función genérica. Qué método será llamado cuando se reciba un mensaje dependerá de los argumentos de clase pasados a la función genérica • Sintácticamente un mensaje enviado en una llamada funcional. El receptor del mensaje es el primer argumento de la llamada a función y pude ser usado naturalmente para enviar mensajes subsecuentes al receptor o pasar el receptor como un argumento de un mensaje subsiguiente. 43 Ejemplo de Carta: CLOS (1) ? (setf test (make-instance 'carta :palo „espada :valor 9)) #<CARD #x22F0A0E> ? (valor test) 9 ? (palo test) ESPADA ? (owner test) VIVI ?(estaBocaArriba test) T ?(setf (valor test) 10) > Error: Undefined function SETF::|COMMON-LISP-USER::RANK| called with arguments (10 #<CARD #x550B06E>) . 44 Ejemplo de Carta: CLOS(2) ?(slot-value test „valor) 9 ? (setf (slot-value test „valor) 10) 10 ? (valor test) 10 ? (estaBocaArriba test) T ? (voltear test) NIL ? (estaBocaArriba test) NIL ? (color test) NEGRO 45 Ejemplo de Carta: CLOS(3) ? (setf other (make-instance 'carta :palo „espada :valor 2 :bocaArriba 'false)) #<CARD #x22F289E> ? (valor other) 2 ? (bocaArriba other) FALSE ? (owner other) VIVI 46 Jerarquía de Clases Carta Palo: {brillo, trebol, corazon, espada} Valor: integer Voltear() <algun código> Carta Mágica Cambiar-Valor(R: integer) <algun código> “define class” <nombre> <superclases> <definición datos> <definición métodos &declaraciones> “define class” Carta Mágica Carta ... ... 47 Herencia • Herencia es la propiedad por las cuales las instancias de una clase hija o una subclases pueden acceder tanto a los datos como al comportamiento (métodos) asociados con una clase padre o superclase. • La Herencia se puede ver como Extensión: el comportamiento y datos asociados con la clase hija es mayor al conjunto de comportamientos y datos asociados con la clase padre. 48 Herencia • La Herencia se puede ver como Especialización: la clase hija es más especializada o restringida que la clase padre. • La Herencia es transitiva • Las subclases pueden sobreescribir el comportamiento de la superclase 49 Herencia & Asociación de Mensaje (una Carta Mágica) Palo: brillo Valor: 9 1 bocaArriba: false (una Carta Mágica) Palo: brillo Valor: 9 bocaArriba: false Cambiar-Valor(1) (una Carta) Palo: brillo Valor: 9 bocaArriba: false Cambiar-Valor(1) Voltear() true 50 Subclase, Subtipo y Substitución • Subclase y subtipo no es exactamente la misma cosa. • El principio de sustitución dice que dadas dos clases A y B, donde B es una subclase de A, una instancia de B puede sustituir a una instancia de A sin ningún efecto observable. • El término subtipo se usa frecuentemente para referirse a una relación de subclase en la cual se mantiene el principio de la sustitución para distinguirlo de una relación más amplia de subclase que no necesariamente satisface ese principio. • Los lenguajes estáticos ponen mucho énfasis en el principio de sustitución que lenguajes dinámicos. 51 Formas de Herencia • La Herencia se usa en una variedad sorprendente de maneras. Algunas categorías generales serían: – – – – – – – – Especialización Especificación Construcción Generalización Extensión Limitación Variación Combinación 52 Especialización (Subtipo) Ventana Mover Resize VentanaTexto Editar • La clase hija es una especialización de la clase padre pero satisface las especificaciones de la clase padre completamente. • El principio de substitución se mantiene. • Es una forma ideal de herencia, los buenos diseños tienden a ella 53 Especificación Abstract Objeto Gráfico Mover //no impl Dibujar //no impl Cubo Circulo Mover Dibujar Mover Dibujar • Las clases hijas implementan el comportamiento descrito pero no implementado en el padre. • Es un caso especial de especialización • La clase padre es una clase abstracta; no se permite crear instancias de ella. • Es usada para garantizar que dos clases mantengan una interfase común. (ej. Los mismos métodos) 54 Construcción Lista Constantes Set Pila Insertar Push • Las clases hijas obtienen la mayoría de su funcionalidad de la clase padre solamente cambiando los nombre de los métodos o modificando los argumentos. • Rompe el principio de sustitución intencionalmente • Es una vía fácil para la construcción de nuevas abstracciones de datos. 55 Generalización Ventana Mostrar Ventana-Coloreada Color Mostrar //sobreesc. • La clase hija modifica o extiende la clase padre para obtener una clase más general de objeto. • Usado cuando se construye sobre una base de clases difíciles de modificar. • Debe ser evitado a favor de invertir la jerarquía de clases y utilizar 56 especialización Extensión Set Insertar Es-elemento? String-Set Buscar-Prefijo • Añade habilidades totalmente nuevas a una subclase: nuevos métodos son añadidos con una funcionalidad que no se relaciona directamente con los métodos existentes. • Debido a que la funcionalidad del padre permanece, obedece el principio de sustitución. 57 Limitación Doble-Cola PushFrente PushAtras Pila PushAtras //error • La subclase modifica o sobrescribe métodos de la clase padre para eliminar la funcionalidad dejando una subclase con un comportamiento más restringido. • Es una contravención explícita del principio de sustitución. • Se usa cuando un grupo base de clases es difícil de modificar. 58 Variación Ratón Marcar-Posicion Seleccionar Tableta Gráfica Marcar-Posicion • Es usada cuando dos clases tienen una implementación similar pero no una jerarquía conceptual • Una de las dos clases se selecciona arbitrariamente como el padre; el código en común se hereda y el código específico se sobrescribe. • Usualmente una mejor alternativa es crear una 59 clase padre común. Combinación Profesor Sueldo Ayudante Estudiante Matricula Materias • Usada para dar a una subclase la combinación de las características de dos o más clases. • Conocido también como herencia múltiple. • Se vuelve un problema cuando ocurren ambigüedades. 60 Herencia en C++ (1) • La herencia se indica en la definición de la clase enumerando las clases padre; se soporta la herencia múltiple. • La palabra reservada public y private en el encabezado de la clase configura al visibilidad de los miembros heredados. Privado se usa para herencia por construcción. class TablePile : public CardPile { ... }; class Set : private List { ... }; • Los constructores de las clases hijas pueden invocar explícitamente los constructores de las clases padres con una cláusula de inicialización: TablePile :: TablePile(int x, int y, int c) : CardPile (x, y) //inicializar padre { ... }; //inicializar hijo 61 Herencia en C++ (2) • Protected es una palabra reservada adicional que puede ser usada con clases formadas por herencia. Los miembros protegidos de una clases son accesible en los métodos de la subclase pero no a las clases cliente. • La palabra reservada virtual indica que la función miembro es muy probablemente sobrescrita por una subclase o es de hecho sobrescribiendo un método de la superclase (es opcional en la subclase) • La semántica de sobescritura en C++ es sutil pero importante; mucho depende en como el receptor ha sido declarado y en el uso de la palabra virtual. 62 Herencia en Java (1) • Las sublclases son declaradas usando la palabra reservada extends class TablePile extends CardPile { ... }; • Todas las clases son dervidas de un objeto raíz llamado Object; si no se menciona una superclase, se asume Object. class CardPile extends Object { ... }; class CardPile { ... }; • Las interfaces son conceptos nuevos en Java, ellas definen un protocolo pero no una implementación. public interface Storing { void WriteFrom (Stream s); void ReadFrom (Stream s); }; 63 Herencia en Java (2) • Una clase puede indicar que implementa una interfaz; las instancias de dicha clases pueden ser valores de una variable declarados del tipo de la interfaz: public class BitImage implements Storing { void WriteOut (Stream s) {...}; void ReadOut(Stream s) {...}; }; • Solo herencia simple es soportada pero una clase puede implementar varias interfaces; las interfaces por otra parte pueden extender múltiples interfaces. • La palabra reservada protected tiene el mismo significado que en C++ 64 Herencia en Java (3) • La idea de subclase por especificación es soportada en Java por el modificador abstract. No es permitido crear instancias de una clase abstracta, solo subclases. Definir una clase como abstracta asegura que será usada solamente como una especificación, no como una implementación. • Un método también puede ser declarado abstract y así no necesita implementación; debe ser sobrescrito por las subclases. • El modificador final usado dentro de una clase indica que la clase no puede tener subclases; el modificador final usado en un método indica que el método no puede ser modificado por un 65 refinamiento. Herencia en Java (4) • El constructor de una clase hija puede siempre invocar al constructor de la clase padre, pero esta invocación siempre toma lugar antes de ejecutar el código del constructor. • Si el constructor del padre necesita argumentos, la seudovariable super es usada como se fuera una función; si no existen llamadas explicitas a super, el constructor por defecto (el que no tenga argumentos) es usado. Class DeckPile extends CardPile { DeckPile (int x, int y, int c) { super(x,y); //initialise parent ... //initialise child }; 66 Herencia en CLOS • La herencia es indicada por la macro defclass al enumerar las clases padres después del nombre de la clase; la herencia múltiple se soporta. (defclass TablePile (CardPile) ...) • Ambos, slots locales y compartidos son heredados y pueden ser sombreados; todos los especificadores de slots son heredados y combinados. • Las opciones de clase, incluyendo la inicialización por defecto son heredadas y combinadas. • Un Lista de precedencia de Clases es calculada para controlar la combinación. • Los métodos se heredan también. 67 Beneficios de la Herencia • Reusabilidad del Software & Compartición de Código: cuando el comportamiento se hereda de otra clase, el código no necesita ser re-escrito; esto ahorra dinero, reduce el tamaño e incrementa la confiabilidad. • Consistencia de Interfaz: cuando muchas clases heredan de una misma superclases es más fácil garantizar que las interfaces a objeto similares son, efectivamente, similares. • Componentes • Prototipos Rápidos • Frameworks 68 Costo de la Herencia • Velocidad de Ejecución: los métodos heredados, que deben tratar con subclases arbitrarias, pueden ser más lentos que código especializado. • Tamaño del Programa: el uso de componentes de software de una librería impone un costo en el tamaño del programa. • Sobrecarga de Paso de Mensajes: el paso de mensajes es más costoso que el llamado a un procedimiento. • Complejidad del Programa: el abuso de la herencia puede obscurecer el flujo del programa. 69 Mecanísmos para Reuso de Software • Los dos mecanismos más comunes para el reuso de software son la composición y la herencia. • La relación “tiene-un” entre dos conceptos se sostiene cuando uno es un componente del otro pero los dos en cualquier caso no son la misma cosa. • La relación “es-un” entre dos conceptos se sostiene cuando uno es una instancia especializada del otro. • El sentido común nos ayuda en la mayoría de casos: un carro tiene un motor un carro es un motor un motor es un carro un perro es un mamífero un perro tiene un mamífero un mamífero tiene un perro 70 Composición y Herencia: el área gris Lista Lista Cons Car Longitud Miembro Cons Car Length Miembro Conjunto Conjunto Elementos: Lista Añadir Tamaño Miembro Añadir Tamaño 71 Composición • Para reusar una abstracción de datos existente una porción del estado de un nuevo tipo de datos es una instancia de la estructura existente. • Las operaciones en el nuevo tipo de datos son implementadas usando las operaciones del tipo existente. • No existen promesa alguna de sustitución. Los dos tipos de datos son completamente distintos. • La composición puede existir tanto en lenguajes orientados a objetos como no orientados a objetos; la única diferencia significativa entre los lenguajes es como la abstracción de datos encapsulada es creada e inicializada. 72 Herencia • Para reusar una abstracción de datos existente, el nuevo tipo de datos se declara como una subclase de un tipo de dato existente. • Las operaciones en el tipo de datos existente son heredadas al nuevo tipo de datos; la nueva clae puede añadir nuevos datos y añadir o sobrescribir métodos. • La herencia lleva la asunción implícita que las subclases son de hecho subtipos; las instancias de una nueva abstracción reaccionan de manera similar a las instancias de la abstracción existente. • La creación apropiada y la inicialización de la abstracción padre depende del lenguaje. • La herencia privada en C++ es apropiada pero rompe el principio de sustitución. 73 Composición y Herencia Contrastadas (1) • La composición es más simple; se puede saber por la declaración nada más que operaciones son soportadas por el tipo de dato • A través de la herencia las operaciones del nuevo tipo de datos son un superconjunto de las operaciones del tipo de dato original; dos o más declaraciones deben ser inspeccionadas para conocer el conjunto completo. • Las abstracciones de datos construidas a través de la herencia son más cortas, las implementaciones también. • La herencia no previene a los usuarios de manipular el nuevo tipo de datos con las operaciones viejas violando el principio de ocultamiento de la información y dejando valores desprotegidos 74 Composición y Herencia Contrastadas (2) • En composición, los tipos de datos existentes se convierten en mecanismos de almacenamiento para nuevos tipos de datos y así en un detalle de implementación. Es más fácil reimplementar el tipo de dato con otra técnica con un impacto mínimo en los usuarios del tipo de dato. • La comprensión y mantenimiento son difíciles de juzgar. La herencia tiene la ventaja de la brevedad del código pero el programador tiene que comprender ambas clases. El código de composición, a pesar de ser más largo, es el único que el programador debe entender. • Las estructuras de datos implementadas a través de herencia tiene una pequeña ventaja en tiempo de ejecución dado que una llamada a función es evitada (usando funciones en línea en C++) 75 Subclases y Subtipos • El concepto de subclase es: una manera de construir nuevos componentes de software a partir de componentes ya existentes. • El concepto de subtipo es más abstracto y tiene que ver con el comportamiento, no con la estructura. Lo que importa es la sustitución. 76 Subclases y Subtipos • Los conceptos de subtipo y subclases no están necesariamente relacionados. Dos clases pueden responder al mismo conjunto de mensajes sin ningún ancestro o implementación común. Si su respuesta a los mensajes es suficientemente similar, pueden substituirse el uno al otro. • Los lenguajes de tipo estático eliminan esta distinción: asumen que todas las subclases son subtipos cuando esto no es completamente cierto cuando las subclases sobrescriben el comportamiento de su padre. 77 Variables y Valores “definevariable” variable” “define My-Card Mi-Carta Card Carta “assign” “make instance” Mi-Carta Magic Card spade, 4, true “make instance” Carta Mágica Espada, 4, true “define variable” <nombre> <tipo> Mi-Carta: Carta (una Carta Mágica) Palo: spade Valor: 4 bocaArriba: true “assign” <variable> <valor> 78 Variables Polimórficas • En los lenguajes estáticos, el tipo estático de la variable es fijo desde la declaración mientras que en los dinámicos lo importante es el tipo que tiene el contenido de la variable. • Una de las características más importantes de los lenguajes OO estáticos es que el tipo estático y dinámico de una variable no son necesariamente el mismo: una variable de tipo/clase A puede contener instancias de cualquier subtipo/subclase de A. Estas variables son llamadas polimórficas. • En lenguajes dinámicos, todas las variables se puede decir que son polimórficas. 79 Unión de Método: Estatico ≠ Dinámico Mi-Carta: Carta (una Carta Mágica) Palo: espada Valor: 4 bocaArriba: true • ¿Un mensaje debe ser unido a un método basado en el tipo estático o dinámico de una variable que es el objeto que recibe el mensaje? Cambiar-Valor(1) 80 Unión de Método Ventana MouseDown(int,int) VentanaTexto MouseDown(int,int) • La diferencia es importante cuando un método es sobrescrito en una subclase • La mayor parte del tiempo se espera la unión al tipo dinámico pero lo contrario a veces es útil. Ventana W; VentanaTexto T(); W = T; W.MousedDown(3,5); 81 Polimorfísmo Reverso List Set Stack List X; Set Y; Stack Z; Set A(); Stack B(); X = A; Y = X; X = B; Z = X; • ¿Puede una instancia de una subclases que es asignada una variable con un tipo estático de su superclase se asignada nuevamente a una variable con el tipo estático de la subclase? • Existen dos preguntas: (1) podemos establecer cuando el valor de la variable es una instancia de la subclase y (2) que mecanismos son necesarios para asignar el valor • Es una pregunta importante cuando se consideran las 82 colecciones. Búsqueda de Método en C++ (1) • Uno de los objetivos primarios de C++ es eficiencia en espacio y tiempo. Por lo tanto muchas de las características son estáticas en vez de dinámicas. • Las variables pueden ser polimórficas solo cuando se utilizan punteros o referencias. • Para variables “normales” el método de unión es estático. • Para punteros y referencias el método de unión es dinámico cuando se utiliza la palabra clave virtual en la declaración del método. • Aún en este caso, el compilador debe verificar la legalidad de los mensajes enviados usando la clase estática del receptor. 83 Búsqueda de Método en C++ (2) class Mammal { public: void speak () {printf ("can't speak") }; }; class Dog1 : public Mammal { public: void speak () { printf("woef") }; void bark () { printf("woef also") }; }; class Dog2 : public Mammal { public: virtual void speak () { printf("woef") }; }; 84 Búsqueda de Método en C++ (3) Mammal fred; Dog1 lassie; Mammal * fido1 = new Dog1; Mammal * fido2 = new Dog2; fred.speak() lassie.speak() fido1->speak() fido2->speak() fido1->bark() "can't speak" "woef" "can't speak" "woef" compiler error 85 Polimorfismo Reverso en C++ • Un dynamic_cast puede convertir entre una clase base polimórfica a una clase derivada o hija; el operando debe ser polimórfico porque la información del tipo en tiempo de ejecución es necesaria y no esta disponible para variables “normales” fido2 = dynamic_cast <Dog2 *> (fido1) • El operador typeid es usado para descubrir el tipo exacto de un objeto; devuelve una referencia a un tipo predefinido type-info typeid(*fido1) • Para salida de diagnóstico, el nombre de la clase puede ser recobrado como una cadena de la siguiente manera. typeid(*fido1).name() 86 Búsqueda de Método en Java • Los mensajes son siempre unidos a los métodos basados en el tipo dinámico del receptor. • Los campos de datos pueden ser sobrescritos en Java pero para accederlos Java se basa en el tipo estático. • Las interfaces definen una organización jerárquica similar, pero independiente de la jerarquía de clases. Las interfaces pueden ser usadas como tipo sen la declaración de variables. Las instancias de clases que implementan una interfaz pueden ser asignadas a estas variables. El tipo estático entonces es el tipo de la interfaz mientras que el tipo dinámico es el tipo de la clase. La unión de método usa el tipo dinámico. 87 Polimorfismo Reverso en Java • En Java todas las variables conocen su tipo dinámico; es posible probar el tipo con el operador instanceOf List L if (L instanceOf Stack) ... • Se permite el polimorfismo reverso con una cast explícito. Cuando el cast es invalido una excepción es arrojada. Stack S S = (Stack) L 88 Búsqueda de Método en CLOS • Cuando una función genérica es llamada, el conjunto de métodos asociados con la función genérica es ordenado para encontrar/crear un método efectivo. Tres pasos son llevados a cabo: – Seleccionar los métodos aplicables: el parámetro specialiser es una clase y el argumento deber ser una instancia de esa clase o de una de sus subclases. – Ordenar los métodos aplicables: cada clase tiene un lista de precedencia de clases que ordena la clase y sus subperclases, la lista de precedencia de clases de los argumentos es usada para ordenar los métodos aplicables encontrados. – Aplicar combinación de métodos para los métodos encontrados 89 Lista de precedencia de clases food fruit beverage sauce alcohol tomato worchester bloody-mary (bloody-mary tomato fruit worchester sauce food alcohol beverage standard-object t) • Una lista de precedencia de clases es calculada usando las siguientes reglas: – Una clase precede a su superclase directa. – El orden en la definición defclass es mantenido en le caso de herencia múltiple – Cada superclase aparece solo una vez. • Si la jerarquía contiene un ciclo, se retorna un error. 90 Combinación de Métodos (1) • La combinación de métodos estándar distingue métodos primarios y tres clases de métodos auxiliares que modifican la acción. Estos métodos son indicados con los calificadores :before :after :around • Cuando todos los métodos aplicables son primarios: – El método más específico es llamado primero – Cuando otros métodos primarios están disponibles estos pueden ser llamados explícitamente usando la función callnext-method la cual en cada paso invocara al siguiente método primario más específico. – El predicado next-method-p verifica si existe otro método – El valor devuelto por el método primario más específico es el valor devuelto por la función genérica. 91 Combinación de Métodos (2) • Cuando existen métodos primarios y auxiliares: – El método :around más específico es llamado y callnext-method puede ser usado para invocar el siguiente método :around más específico. – Cuando no hay más métodos :around, el método call-next llamará a cada método :before en orden – Luego del último método :before el método primario más específico es llamado, el cual a su vez usa el método call-next- para invocar otros métodos primarios aplicables. – Cuando ya no existen métodos primarios, todos los métodos :after son llamados en orden – Los métodos :before y :after son usados para efectos colaterales, el valor devuelto es ignorado. 92 Combinación de Métodos R1 R1 R2 R2 B1 B2 B3 P1 P3 P2 P2 Llamada a call-next-method Retorno de call-next-method Métodos llamados en secuencia P3 A2 A1 Ri :around Ai: after Bi :before Pi: primario 93 Ejemplo de Combinación de Métodos (1) (defclass person () ((name :initarg :name :reader name))) (defclass phd-holder (person) ()) (defclass professor (phd-holder) ()) (defmethod printname ((p person)) (format t (name p))) (defmethod printname ((p phd-holder)) (format t "Dr. ") (call-next-method)) (defmethod printname ((p professor)) (format t "Prof. ") (call-next-method)) 94 Ejemplo de Combinación de Métodos (2) ? (setf bob (make-instance 'person :name "Bob Brown")) #<PERSON #x202A6D6> ? (setf viv (make-instance 'professor :name "Viviane Jonckers")) #<PROFESSOR #x202A876> ? (printname bob) Bob Brown NIL ? (printname viv) Prof. Dr. Viviane Jonckers NIL 95 Ejemplo de Combinación de Métodos (3) (defclass employe () ((name :initarg :name :reader name) (salary :initarg :salary :accessor salary) (department :initarg :department :accessor department))) (defclass department () ((name :initarg :name :reader name) (expenses :initarg :expenses :accessor expenses :initform 0) (manager :initarg :manager :accessor manager))) 96 Ejemplo de Combinación de Métodos (4) (defmethod give-raise ((e employe) amount) (setf (salary e) (+ (salary e) amount))) (defmethod give-raise :around ((e employe) amount) (if (> (+ (salary e) amount) (salary (manager (department e)))) (format t "cannot give raise") (call-next-method))) (defmethod give-raise :before ((e employe) amount) (setf (expenses (department e)) (+ (expenses (department e)) amount))) (defmethod give-raise :after ((e employe) amount) (format t "raise is given")) 97 Ejemplo de Combinación de Métodos (5) ? (setf sales (make-instance 'department :name 'sales)) #<DEPARTMENT #x202D476> ? (setf bill (make-instance 'employe :name "bill gates" :salary 40000 :department sales)) #<EMPLOYE #x202D66E> ? (setf bob (make-instance 'employe :name "bob brown" :salary 30000 :department sales)) #<EMPLOYE #x202D866> ? (setf (manager sales) bill) #<EMPLOYE #x202D66E> 98 Ejemplo de Combinación de Métodos (6) ? (give-raise bob '5000) raise is given 35000 ? (salary bob) 35000 ? (expenses sales) 5000 ? (give-raise bob '10000) cannot give raise NIL 99 Reemplazo y Refinamiento • Cuando una subclase simplemente añada datos o métodos a una superclase, los datos y métodos del padre y del hijo son diferentes. • Cuando una subclase sobrescribe los datos o métodos de la clase padre tanto la semántica del refinamiento y reemplazo son posibles. • El reemplazo de métodos implica que el código del padre nunca se ejecuta cuando se manipulan instancias del hijo. • Con el refinamiento de métodos, el método heredado del padre es ejecutado como parte de la ejecución del método del hijo. • El refinamiento de datos puede significar diferentes cosas: tener ambos o combinar valores por defecto y 100 otras opciones. Reemplazo y Sustitución • La semántica del reemplazo no encaja con el principio de sustitución: cuando las subclases son libres de sobrescribir los métodos existentes con métodos que pueden realizar acciones arbitrarias, no existe garantía de que el comportamiento de la clase hija sea igual al comportamiento de la clase padre. • La mayoría de lenguajes ignora este problema y deja al programador realizar las decisiones correctas. • En Eiffel un programador puede añadir aserciones (condiciones acerca del estado del objeto) a un método. Las aserciones son heredadas y controladas aún cuando el método se sobrescriba. 101 Refinamiento • La Semántica del refinamiento soluciona el conflicto entre sobrescritura y sustitución: en vez de remplazar el código de la clase padre, la acción descrita en la clase hijo es combinada con las acciones descritas en la clase padre, asegurando un mínimo nivel de compatibilidad. • La utilidad de la semántica de refinamiento se nota especialmente en la creación de nuevos objetos: las inicializaciones tanto en la clase padre y la clase hijo deben tener lugar. • La mayoría de lenguajes soportan la semántica de refinamiento a través de los mecanismos que permiten que un método sobrescrito 102 invoque al mismo método de la clase padre. Reemplazo en C++ • La sobrescritura en C++ es complicada por su relación con la sobrecarga y la declaración de métodos virtuales/no virtuales. • Reemplazo simple ocurre solamente cuando los argumentos de una clase hija no son los mismos en tipo o número que los argumentos en la clase padre y cuando el método es declarado virtual. • Sin la primera condición, la sobrecarga toma lugar: hay dos métodos distintos. • Sin la segunda condición la unión de método es estática, así que el método de la clase padre el 103 que se ejecuta de cualquier manera. Refinamiento en C++ • En C++ la invocación de un método puede ser calificada a precisar específicamente la clase desde la cual el método es derivado. La calificación se escribe como nombredeclase::nombredemetodo • La calificación puede ser usada para simular mecanismos de refinamientos en la sobrescritura. Un método sobrescrito puede explícitamente invocar el de la clase padre para asegurar que los dos se ejecuten. • Los constructores siempre usan refinamiento en vez de reemplazo: el constructor de una clase padre puede ser invocado explícitamente a través de una cláusula de inicialización en el constructor de la clase hija, sino el constructor por defecto del padre es invocado. 104 Reemplazo y Refinamiento en JAVA • El reemplazo ocurre cuando un método tiene la misma firma que un método en la clase padre; sino se produce sobrecarga. • Los campos de datos pueden ser remplazados o sombreados pero el reemplazo no es dinámico, el campo de datos seleccionado será determinado por el tipo estático de la variable, no por su tipo dinámico. • La palabra reservada final puede explícitamente deshabilitar la sobrescritura. • La seudo-variable super provee un mecanismo para simular el refinamiento. 105 Reemplazo y Refinamiento en CLOS • El mecanismo de combinación de métodos explicado anteriormente puede simular la semántica del refinamiento a través de uso de call-next-method. Sin la llamada a call-nextmethod, la semántica de reemplazo ocurre debido a que el método más específico será el llamado. • Tanto slots locales como compartidos pueden ser sobrescritos o sombreados. Las opciones de slots y las opciones de clase son apropiadamente combinadas cuando las instancias son creadas. 106 Herencia múltiple • Varias clasificaciones son posibles para una misma entidad: Ecuatoriano, hombre, profesor, padre, etc… • La herencia múltiple es apropiada cuando la relación “es-un” se mantiene: un pintor de retratos es un pintor y es un artísta • La herencia simple se realiza frecuentemente para especializar, la herencia múltiples se realiza para combinar. • La herencia múltiple es una característica útil y poderosa en los lenguajes, pero crea muchos problemas para el implementador del lenguaje. 107 Ambigüedad de Nombres (1) MazoCarta ObjetoGráfico Dibujar Dibujar MazoGráfico • Los dós significados de dibujar chocan: dibujar • El problema lo tiene la clase hija, no las clases padre • Una combinación de renombre y redefinición es la solución estándar en este caso. 108 Ambigüedad de Nombres (2) Profesor Estudiante ImpTitulo ImpTitulo Ayudante • Un solo significado conceptual para ImpTitulo pero un método diferente para cada clase padre • Renombrar no es la solución, se necesita un mecanismo que permita al la clase hija el decidir entre precedencia o combinación. 109 Herencia de Ancestros Comunes Persona Nombre ImpTitulo Profesor Estudiante ImpTitulo ImpTitulo Ayudante • Cuando solamente el comportamiento es heredado de un ancestro común , la técnica de resolución usual es usada. • Cuando los campos de datos son los involucrados, se decidir si el campo de datos es heredado una o dos veces y si los constructores deben ser invocados una o dos veces. 110 Herencia Múltiple en C++ • Tanto renombrar y combinación de métodos se pude suar a través de la calificación. class GraphicalDeck: public GraphicalObject, public CardDeck { public: void Draw() { GraphicalObject::Draw() }; Card ChooseCard() { CardDeck::Draw() }; ... class TeachingAssistant: public Teacher, public Student { public: PrintTitle() { Teacher::PrintTitle(); Print(" ") Student::PrintTitle(); }; ... 111 Herencia Múltiple y Sobrecarga Paramétrica en C++ • En C++ los métodos con el mismo nombre pero diferentes firmas son distintos. El compilador escoge el correcto basado en el los argumentos en la llamada. • Cuando dos clases padres definen un método con el mismo nombre pero diferentes parámetros se espera que la clase hija hereda ambos métodos. • Pero el compilado no podrá decidir entre el métodos que encaje con el tipo del argumento y el método encontrado es el primero encontrado y es aplicable con conversión implícita de 112 parámetros; sino un error es arrojado. Herencia Múltiple en Java • Java no soporta herencia múltiple como tal, pero: – Una clase puede implementar múltiples interfaces – Una interfaz puede extender múltiples interfaces – Una clase puede extender otra clase e implementar una interfaz • Cuando una clase implementa múltiples interfaces, los choques en nombre no afectan dado que la implementación del método esta dada en la clase de cualquier manera. 113 Herencia Múltiple en CLOS • El método de combinación explicado anteriormente implementa precisamente una manera estándar de combinación de métodos. • Cuando la combinación estándar de métodos no produce el resultado esperado, nuevos tipos de combinación de métodos pueden ser usados a través de (muy sofisticado, pero muy complicado) define-method-combination macro 114 Distribución en Memoria y Herencia Ventana int altura int ancho VentanaTexto char *contenido int ubicacion • ¿Cuanto espacio debe ser reservado para una variable de tipo Ventana? • Tres posibles respuestas: – Espacio estático mínimo – Espacio estático máximo – Reserva dinámica de memoria (usar punteros) 115 Reserva de Espacio MínimoMáximo Estático • Reservar espacio para los datos de la clase base solamente. • Usada en C++ para variables que no usan punteros o referencias que son almacenadas en el stack • Cuando se asigna una instancia de una subclase (más grande) a una variable del tipo de la clase base se pierden datos. • Esta pérdida no es problema ya que la unión de métodos es estática para estas variables. • Reservar el espacio máximo que la variable puede contener (uniones y registros variant) • Este acercamiento no se usa en ninguno de los principales lenguajes OO. Debido a que el tamaño máximo solo puede ser conocido cuando el programa completo es escaneado, cualquier tipo de compilación separada es imposible. 116 Reserva Dinámica de Memoria • Solo se crea espacio para un puntero en el stack; el espacio para las instancias es reservado en el heap donde las instancias son creadas. • Es usado en Java, SmallTalk, CLOS y en C++ cuando las variables son punteros o referencias. • En este acercamiento la asignación estándar generalmente usa semántica de punteros, el puntero, en vez del valor es transferido. • Hacer copias reales, tanto superficiales como profundas deben ser hechas explícitamente (ej. Sobrecarga en C++ o el método clone de Java) • La igualdad se vuelve un problema (ej. == sobrecarga en C++ o equal/eq en LISP) 117 Revisando el Polimorfismo • El Polimorfismo (significa “muchas formas”) tiene muchas caras: – – – – – – – – – Variables polimórficas Sobrecarga de Operadores y Coerción Sobrecarga Paramétrica Polimorfismo Ad hoc Sobrescritura Métodos diferidos Polimorfismo puro Funciones genéricas y estructura de datos. Plantillas y genéricos 118 Variables Polimórficas • Término usado en lenguajes OO estáticos (C++, Java) para referirse a variables en las cuales el tipo dinámico de la variables puede se cualquier subclase del tipo estático. • En Java todas las variables de una clase son polimórficas, en C++ solo los punteros y referencias a variables son polimórficas. • En lenguajes dinámicos cualquier variable es polimórfica, aquí la relación de subclase no entra en juego, las variables simplemente no tienen tipo estático. 119 Sobrecarga • Un procedimiento, función, método u operador esta sobrecargado si existe una o algunas implementaciones alternativas (cuerpos ) para ellos. • Ocurre en lenguajes OO (Java, C++), en lenguajes imperativos (Ada) y lenguajes funcionales (Lisp) • Ante la llamada a procedimiento los mecanismos de resolución determinan la implementación a usar. • Las diferentes implementaciones asociadas con el mismo nombre no necesariamente compartir similaridad semántica. Por lo tanto el término polimorfismo ad hoc es usado en algunas ocasiones. 120 Sobrecarga basada en ámbito • La sobrecarga puede estar basada en el ámbito: una función f definida dentro de una función g es distinta de una función f definida dentro de una función :h, el método :enviarflores en la clase florista es distinta del método :enviarflores en la clase esposa. • Para un procedimiento o función el ámbito de la llamada será usado en el proceso de resolución; esto se realiza típicamente en el tiempo de compilación dada que la mayoría de lenguajes definen el ámbito por la sintaxis del lenguaje. • Todos los lenguajes OO permiten que existan métodos con el mismo nombre en casos no relacionados. Es la clase receptora del mensaje la que determina el método que va a ser ejecutado. En el caso de clases no relacionadas esto es también una decisión de tiempo de compilación. 121 Sobrecarga Paramétrica • Un estilo de sobrecarga donde los procedimientos en el mismo contextos se permite que compartan un nombre y se puede desambiguar por el número y tipo de los argumentos • En lenguajes estáticos el proceso de resolución, que es la selección del cuerpo que encaje con una invocación particular, es realizado en tiempo de compilación basado en el tipo estático y los argumentos. El mejor ejemplo son los operadores matemáticos. • El principio de sustitución introduce una nueva forma de coerción de objetos en lenguajes OO. • Resolver el nombre de un procedimiento sobrecargado puede ser muy complejo cuando la coerción automática es soportada; errores en tiempo de compilación ocurrirán cuando el proceso de resolución tenga problemas. 122 Redefinición • Ocurre cuando una clase hija define un método con el mismo nombre que un método en la clase padre, pero con una firma diferente. • Los lenguajes de programación utilizan el modelo de mezcla (Java) o el modelo jerárquico (C++) para resolver la redefinición de nombre. • El modelo de mezcla une todas las diferentes implementaciones encontradas en todos los ámbitos en una sola colección y el que más se asemeje es seleccionado. • En el modelo jerárquico cada ámbito es examinado en turno y tan pronto como en un ámbito es encontrada una implementación que se asemeje, esa será la seleccionada. 123 Sobrescritura • Un mecanismo de lenguajes OO donde en una subclase un método es definido con el mismo nombre y firma de un método que es heredado de la clase padre. • Ambas semánticas, de reemplazo y refinamiento, pueden ser aplicables. • La sobrescritura es usualmente transparente para el usuario de la subclase y las dos funciones son a menudo vistas semánticamente como una sola entidad. • La semántica de reemplazo hace que la semántica de la similaridad no este necesariamente presente. • Algunos lenguajes requieren que la sobrescritura sea especificada explícitamente con una palabra reservada 124 en el padre, el hijo o en ambos. Métodos Diferidos • Es un caso especial de sobrescritura. El comportamiento definido en la clase padre es esencialmente nulo, toda la actividad útil es definida en la clase hija. • Se conoce en C++ como métodos virtuales puros (virtual ... = 0), en Java como métodos abstractos • La primera ventaja es conceptual: el método puede ser introducido a un nivel mayor de abstracción que en el que puede ser implementado. • En lenguajes estáticos tiene un propósito práctico también: el compilar puede verificar estáticamente los mensajes enviados a una variable polimórfica. 125 Polimorfismo Puro • Este término se reserva para situaciones donde una función pude ser usada en con un variedad de argumentos y donde el mismo cuerpo de función es ejecutado en cada caso. • Ocurre frecuentemente en lenguajes dinámicos (Lisp, Scheme) ej. Funciones que manipulan una lista arbitraria de parámetros. En este contexto los términos funciones genéricas y estructuras de datos genéricas son usados. • Ocurren en lenguajes OO cuando un método es definido en una superclases, el método como tal es heredado por las subclases pero el comportamiento es personalizado cuando este envías más mensajes al receptor que entonces se unen dinámicamente. 126 Genéricos y Plantillas • Proveen una manera de parametrizar una clase con un tipo. • Introducidos en lenguajes estáticos para permitir la creación y manipulación de estructura de datos genéricas tal como un contenedor reusable. • Existe en C++ bajo el nombre de plantillas, es usado extensamente en la librería de plantillas estándar. También se conoce en Ada como paquetes generales. • Es el compilador el que genera el código correcto para cada “instancia” de una plantilla; así el cuerpo ejectudo es no genérico sino 127 específico. Plantillas C++ template <class T> class List { public: void add(T); T firstElement; List<T> * nextElements; }; List<int> A; List<double> B; template <class T> int length(List<T> & X) { if (X==0) return 0; return 1 + length(X.nextElements); }; 128 Clase Contenedor: Caso de Estudio • Las estructuras de datos estándar como listas, colas, conjuntos, árboles y diccionarios deberían ser reutilizables entre diferentes proyectos. • Al explorar el problema de desarrollar clases contenedoras reusables, se necesita contestar a 3 preguntas: – ¿Es posible construir un contenedor de propósito general que sea independiente del tipo de sus elementos? – ¿Puede este contenedor mantener solo un tipo de valores (contendor homogéneo) o pude mantener diferentes tipos (contenedor heterogéneo)? – ¿Es posible dar acceso a los elementos contenidos sin remover los elementos o sin exponer los detalles de implementación interna del contenedor? 129 Soluciones Pre OO para el Contenedor • Tanto abstracciones homogéneas como heterogéneas pueden ser construidas bastante fácilmente en lenguajes dinámicos • En lenguajes estáticos los contenedores se pueden construir, pero: – El tipo de elementos del contenedor es fijo, así que su reusabilidad es limitada – Registros Variant o uniones ayudan hasta cierto punto a construir contenedores heterogéneos. Pero solo se permite un número finito de alternativas y es complicado probar y extraer los valores – Problemas ocultando los detalles de implementación. Ej. Al crear un lazo que recorra el contenedor se debe crear una variable que revele la estructura de la implementación interna. 130 Soluciones OO para el Contenedor • En lenguajes OO estáticos se pueden construir contenedores homogéneos y heterogéneos, pero: – El lenguaje soporta sustitución y todos los valores a ser contenidos son instancias de subclases con una superclase común – Los objetos conocen su propio tipo (dinámico); pruebas explícitas y conversiones son todavía necesarios para extraer los valores pero puede estar ocultos tras la subclase. • Iteradores explícitos pueden ser introducidos para recorrer los elementos individuales del contenedor sin necesidad de exponer la estructura de la implementación del contenedor. • Para construir listas homogéneas las plantillas son LA solución. 131 Visibilidad y Dependencia • La naturaleza interconectada del software es uno de los mayores obstáculos en construir componentes de software reusables. Ambas, visibilidad y dependencia, se relacionan con al interconectividad. • La visibilidad es una caracterización de nombres, ej. Ámbito de las variables. Esta relacionada con la conectividad porque cuando la visibilidad de los nombres es controlada y reducida, la manera en que un objeto puede ser usado se controla también. • La dependencia ocurre cuando una unidad de software no pude existir significativamente sin otra unidad. Se relaciona con la conectividad porque cuando la segunda unidad cambia, la primera deja de funcionar. 132 Aparejamiento y Cohesión • El aparejamiento y la cohesión fueron introducidas en el contexto de la programación modular y revisadas luego en el contexto de la programación OO. • El aparejamiento describe las relaciones entre módulos o clases, la cohesión describe las relaciones dentro de ellos. • La reducción de la interconectividad entre módulos o clases se logra a través de una reducción del aparejamiento. • Por otro lado, módulos y clases bien diseñadas deberían tener un propósito: en un buen diseño los elemento dentro de una clase o módulo deben tener cohesión interna. 133 Variedades de Aparejamiento (1) • (-) Interno de datos: una clase modifica directamente ls valores locales de otra clase. • (-) Dato Global: dos o más clases están unidas a través de su dependencia de una estructura de datos global • (-) Control (o secuencia): una clase debe realizar alguna operación en un orden específico, pero otra clase es responsable de la activación de estas operaciones. 134 Variedades de Aparejamiento (2) • (-) Componente: una clase mantiene un campo de datos que es una instancia de otra clase (+ si una sola vía) • (+) Parámetros: una clase invoca los servicios de otra y las únicas relaciones entre sus miembros son el número y tipo de los argumentos y el valor de retorno. • (+) Subclase: la relación de una clase con su padre 135 Variedades de Cohesión (1) • (-) Coincidental: los elementos en un módulo estan agrupados sin razón aparente, muchas veces el resultado de “modularizar” un programa grande. • (±) Lógica: existe una conexión lógica entre los elementos de un módulo pero no hay conexión en sus datos o control, ej. Una librería de funciones matemáticas. • (±) Temporal: los elementos de un módulo están agrupados porque todos ellos deben ser usados aproximadamente al mismo tiempo. Ej. El módulo que permite la inicialización del 136 programa. Variedades de Cohesión (2) • (±) Comunicación: los elementos de un módulo están agrupados porque todos accesan los mismos datos o dispositivos. Ej. El manejador de un dispositivo. • (±) Secuencial: los elementos están agrupados en un módulo porque necesitan ser activados en un oren particular, muchas veces el resultado de evitar el aparejamiento secuencial. • (+) Funcional: todos los elementos del módulo o clase se relacionan con la ejecución de una sola función. • (+) Datos: cuando el módulo define un conjunto de valores y exporta los operaciones para manipular esos datos, ej. Cuando se implementa tipos de datos abstractos. 137 Aparejamiento y Cohesión en Sistemas OO • Una clase puede ser vista como una extensión de un “módulo pequeño” con una diferencia importante, un programa OO es habitado por un número de instancias de las diferentes clases presentes en el programa. • Las guías de diseño para módulos pueden traducirse fácilmente en guías de diseño para objetos. • Los objetos de distintas clases deberían tener tan poco aparejamiento como sea posible. • Por otra parte cada objeto debería tener un propósito definido y cada método debería colaborar con ese propósito de alguna manera. 138 La ley de Demeter (1) • La ley de Demeter es una guía práctica que trata de reducir el grado de aparejamiento entre objetos al limitar sus interconexiones. • En una primera forma la ley dice lo siguiente: En un método M adjunto a una clase C, solo métodos definidos por las siguientes clases pueden ser usados: (1) Las clases variables de instancia de C (2) Las clases de los argumentos de M (incluyendo C misma, objetos globales y cualquier objeto creado lógicamente en M) 139 La ley de Demeter (2) • Cuando se rescribe en término de instancias en vez de método, la ley dice: Dentro de un método M es permitido acceder o enviar mensaje a los siguientes objetos: (1) Los argumentos de M (incluido M) (2) Variables de instancia del receptor del método (3) Variables globales (4) Variables temporales creadas dentro del método • En su versión fuerte las variables de instancia de la superclases son excluidas 140 Visibilidad a nivel de clase versus a nivel de objeto Point X : int Y : int A:Point X:2 Y:3 B:Point X:1 Y:5 • Visibilidad a nivel de clase permite que un objeto tenga acceso al estado interno de objetos hermano. La visibilidad es controlada a nivel de la clase • Visibilidad a nivel de objeto no permite que el estado interno de objetos hermano, la visibilidad esta controlada a nivel de instancia 141 Subclases Clientes y Usuarios Clientes Window Move Resize TextWindow Edit Resize • Una clase tiene una cara Game pública: los datos y métodos W: Window que pueden ser accedidos y Move usados por clientes de esa clase. • Una clase tiene su propia cara privada: los datos y métodos que pueden ser accedidos dentro de la clase solamente. • Una clase tiene una tercera cara: las características que pueden ser accesible de subclases pero no de usuarios 142 Control de Acceso en C++ • Las palabras reservadas public, private y protected son los especificadores de acceso estándar; la palabra clave final añade protección contra cambios de los valores de los miembros de datos. • El mecanismo de acceso protege contra accidentes, no contra usuarios maliciosos; el uso de punteros y referencias por ejemplo pueden fácilmente violar el sistema de protección. • Los especificadores de acceso operan a nivel de clase; miembros privados y protegidos de instancias hermanas pueden ser accedidos. • Una función, clase o método puede ser declarado friend de una clase y puede tener acceso a miembros privados y protegidos. 143 Acceso y Visibilidad en C++ • Los especificadores de acceso controlan el acceso, no la visibilidad; en el siguiente ejemplo el control de la visibilidad da como resultado una actualización de la variable global I y no un error por ser I privado. Int I: // global variable class A { private: int I; }; class B : public A { public: void f () { I++ };!! error A::I is private }; 144 Espacio de Nombres en C++ • El espacio de nombres es una funcionalidad que reduce la proliferación de nombres globales. Antes la palabra reservada static podía ser usada para limitar el ámbito de un nombre a un archivo, pero cuando un nombre necesitaba ser compartido entre dos o más archivos, tenía que ser global. • Estos nombres ahora pueden ser encerrados en una definición de espacio de nombres y el espacio de nombres puede ser incluido por una directiva explícita en otro archivo o un ítem sencillo puede ser importado. Namespace myLib class A { ... }; }; { using namespace myLib; using myLib::A; using myLib::A test; 145 Control de Acceso en Java • Las palabras reservadas public, private y protected son los especificadores estándar, la palabra reservada final añade protección adicional. • Los especificadores trabajan a nivel de clase; miembros privados y protegidos de instancias hermanas pueden ser accedidos • Un package agrupa clases e interfaces. Es usado como un espacio de nombres. Un paquete puede ser importado como un todo o clases o interfaces individuales pueden ser importadas. package myLib; class A { ... }; import myLib.*; import myLib.A; new myLib.A(); 146 Control de Acceso en CLOS • Las opciones de slot :reader, :writer y :accessor pueden ser usadas para controlar el acceso; sin embargo la función del sistema slot-value da acceso abierto a todos los valores. • Los métodos son siempre “public” 147