De Problemas a Soluciones “Half the battle is knowing what problem to solve” Data Structures and Algorithms (A.A. Aho, J.E. Hopcroft & J.D. Ullman) La Programación Orientada a Objetos: Introducción Prof. Franco Guidi Polanco Escuela de Ingeniería Industrial Pontificia Universidad Católica de Valparaíso, Chile [email protected] De http://pbfcomics.com/ Actualización: 02 de agosto de 2007 Franco Guidi Polanco (PUCV-EII) De Problemas a Soluciones Los requerimientos: son incompletos usualmente están equivocados son engañosos (y los usuarios también) no cuentan la historia completa Diseño: determinar un modelo que nos permita resolver el problema. Implementación: determinar la forma de representar el modelo que describe el problema (y la solución): 02/08/2007 2 Los Requerimientos Análisis: entender el problema y sus requerimientos Franco Guidi Polanco (PUCV-EII) 02/08/2007 Por lo tanto, los requerimientos siempre cambian 3 Franco Guidi Polanco (PUCV-EII) 02/08/2007 4 El Desarrollo de Proyectos Metodologías y Modelos La Ingeniería de Software provee: Metodologías de desarrollo. Desarrollo en cascada Proceso Unificado etc. que propuso el 1 Lo comité de proyecto que se especificó 2 Lo como requerimiento 3 Modelos para soportar el desarrollo Lo que diseñó el analista Se estudiarán en detalle en curso de Modelamiento de Sistemas Especificación estructurada (DFDs, MER, etc.) UML etc. Lenguajes para la implementación que desarrollaron 4 Lo los programadores Franco Guidi Polanco (PUCV-EII) 5 Lo que se instaló al usuario C, C++ Java etc. que necesitaba 6 Lo el usuario 02/08/2007 5 Cecilia Serrano (Miss Chile 1979) Franco Guidi Polanco (PUCV-EII) 02/08/2007 6 Divide et Impera: Descomposición Funcional “Divide et Impera” The Beatles Grease Descomposición en funciones: el analista descompone el problema en una serie de pasos que permiten resolverlo. Los pasos son refinados sucesivamente, hasta llegar a funciones elementales. Antes de la Programación Orientada a Objetos ¿Costo? Teleserie de Cara al Mañana (1982) 1a Teletón (1978) Franco Guidi Polanco (PUCV-EII) 02/08/2007 8 Descomposición Funcional La Encapsulación como Agrupación de Funciones La encapsulación permite la agrupación de ideas relacionadas en una unidad, la que posteriormente puede ser referida por un solo nombre. ... ingresarDatos(datos); calcularMedia(datos, media); calcularDE(datos, de); mostrarResultados(media, de); ... procedimiento ingresarDatos(datos:arreglo) { i=0; mientras i <= 10 hacer { leer datos[i] i = i + 1; } } El concepto surgió en la década del ’40 con la invención de la subrutina: los programadores se dieron cuenta de que un mismo patrón de instrucciones se podía repetir muchas veces en un programa. procedimiento calcularMedia(datos:arreglo, media:float) { i=0; suma = 0; mientras i <= 10 hacer { suma = suma + datos[i] i = i + 1; } media = suma / 10 } ... Franco Guidi Polanco (PUCV-EII) 02/08/2007 9 Descomposición Funcional: Reutilización de Código Programa original . . i=1 mientras i<=10 { si a[i]=10 i=i+1 } . . i=1 mientras i<=10 { si b[i]=10 i=i+1 } . . i=1 mientras i<=10 { si c[i]=10 i=i+1 } . hacer entonces a[i]=0 hacer entonces b[i]=0 hacer entonces c[i]=0 Franco Guidi Polanco (PUCV-EII) 02/08/2007 10 Ventajas de Funciones o Subrutinas Ahorro de memoria de computador (código fuente de programas más corto). Programa con subrutina . . HacerCeros(a) . . . HacerCeros(b) . . . HacerCeros(c) . . . Código fuente más “entendible”: una subrutina agrupa un conjunto de instrucciones en un “concepto” que una persona puede considerar y manejar como una sola idea (en el ejemplo, HacerCeros). Procedimiento HacerCeros(x:arreglo) i=1 mientras i<=10 hacer { si x[i]=10 entonces x[i]=0 i=i+1 } Franco Guidi Polanco (PUCV-EII) 02/08/2007 11 Franco Guidi Polanco (PUCV-EII) 02/08/2007 12 Reutilización antes de la POO: Tipos Abstractos de Datos Lenguajes de Programación Antes de la POO los lenguajes de programación eran procedimentales. ADT NOTA: NO LO INTENTE Aproximación: (Abstract Data Type) Imagine programar en Java, usando sólo métodos estáticos, con instancias de clases que no tengan métodos. Franco Guidi Polanco (PUCV-EII) Tipo de dato (No provisto por el lenguaje de programación) + Operaciones Por separado, pero asociadas. 02/08/2007 13 Franco Guidi Polanco (PUCV-EII) 02/08/2007 14 La Programación Orientada a Objetos (POO) Pulp Fiction Q. Tarantino (1994) Tragedia Transbordador Challenger (1986) El paradigma de la Orientación a Objetos es sucesor de la descomposición funcional Se centra en el concepto de Objeto: Tim Berners-Lee (CERN-1989) Objeto = Datos + Métodos La Programación Orientada a Objetos Derrumbe Muro Berlín (09/09/1989) Cecilia Bolocco Miss Universo (1987) (Definición tradicional) Los objetos: son responsables de si mismos. “saben” de qué tipo son. conocen su propio estado (datos). contienen el código que les permite actuar. Franco Guidi Polanco (PUCV-EII) 02/08/2007 16 POO versus Descomposición Funcional ¿Cómo identificar Objetos? El modelo de programación funcional mantenía centralizadas las responsabilidades: En problemas pequeños una técnica sencilla se basa en la identificación de sustantivos y verbos : Una cena en la cual un garzón pide a cada comensal lo que se servirá, y luego les trae los platos solicitados. Los sustantivos pueden ser objetos Los verbos pueden ser métodos El modelo de programación OO provee delegación de responsabilidades: Una cena en la cual a los comensales se les indica la distribución del buffet. Ellos se sirven a su propio gusto. Franco Guidi Polanco (PUCV-EII) 02/08/2007 17 Objetos Franco Guidi Polanco (PUCV-EII) 02/08/2007 18 Visión de Objetos Una buena forma de concebir un objeto es pensar en él como una entidad con responsabilidades. Las responsabilidades determinan el comportamiento del objeto. Debe existir una forma para comunicar a un objeto qué debe hacer. Esta comunicación se logra por medio del conjunto de métodos que un objeto ofrece para que otros puedan invocar. El conjunto de estos métodos se denomina interfaz pública del objeto. Martin Fowler identifica tres perspectivas para describir los objetos: Nivel conceptual: un objeto es un conjunto de responsabilidades. Nivel especificación: un objeto es un conjunto de métodos (comportamientos), que pueden ser invocados por otros objetos o por si mismos. Nivel implementación: un objeto es código y datos, e interacciones computaconales entre ellos. Martin Fowler Franco Guidi Polanco (PUCV-EII) 02/08/2007 19 Franco Guidi Polanco (PUCV-EII) 02/08/2007 20 Perspectivas para Describir Sistemas OO Nivel Conceptual Perspectivas para Describir Sistemas OO: Ejemplo Sistema de docencia, que mantiene datos de alumnos y registra sus inscripciones en cursos. Análisis Nivel Especificación Codificación (Lenguajes de programación e.g. Java, C++, C#, etc.) Diseño (Lenguajes de modelamiento e.g. UML) Nivel Conceptual Nivel Especificación Nivel Implementación Nivel Implementación Franco Guidi Polanco (PUCV-EII) 02/08/2007 21 Franco Guidi Polanco (PUCV-EII) Ejemplo: Nivel Conceptual 22 Ejemplo: Nivel Especificación Responsabilidades: Clase Alumno: Alumno: Mantener datos de un alumno (rol y nombre). Debe validar el rol del alumno. Curso: 02/08/2007 public public public public setRol(numero: int, verificador: int) setNombre( nombre: String) getRol(): String getNombre(): String Clase Curso: Mantener datos de un curso (nombre) Mantener la lista de los alumnos que se inscriben en el curso, verificando que estos no se repitan al momento de agregarlos. Retornar alumnos, buscándolos por rol del alumno. Franco Guidi Polanco (PUCV-EII) 02/08/2007 public setNombre(nombre: String) public addAlumno(alumno:Alumno):boolean public getAlumno(rol: String):Alumno 23 Franco Guidi Polanco (PUCV-EII) 02/08/2007 24 ¿Es necesario tomar precauciones, si el análisis inicial está bien hecho? Ejemplo: Nivel Implementación public class Alumno{ private String rol, nombre; public void setRol(int numero, int verificador){ if( sumaDigitos(numero) == verificador ) this.rol = rol +”-”+verificador; } public void setNombre(Sting nombre){ this.nombre = nombre; public class Curso{ } private String nombre; public String getRol(){ private Vector alumnos; return rol; } public void setNombre(String nombre){ public String getNombre(){ this.nombre = nombre; return nombre; } } public boolean addAlumno(Alumno alumno){ private int sumaDigitos(int numero){if( getAlumno( alumno.getRol() )== null ){ ... alumnos.add( alumno ); } return true; } } “All systems change during their life cycles. This must be borne in mind when developing systems expected to last longer than the first version.” Ivar Jacobson. “Object Oriented Software Engineering a Use Case Driven Approach”, Addison Wesley, 1992, return false; } public Alumno getAlumno(String rol){ ... } } Franco Guidi Polanco (PUCV-EII) 02/08/2007 25 POO y Encapsulación Ejemplo: public class Punto{ private int coordA, coordB; public void setXY(int x, int y){ coordA = x; coordB = y; } public int getX(){ return coordA; } public int getY(){ return coordB; } } Sin embargo, se refiere además a ocultamiento de: implementación de métodos tipo, clases derivadas detalles de diseño reglas de instanciación Franco Guidi Polanco (PUCV-EII) 02/08/2007 02/08/2007 26 Encapsulación como Ocultamiento de Datos Tradicionalmente se asocia a “ocultamiento de datos” (estado) de un objeto. Franco Guidi Polanco (PUCV-EII) 27 Franco Guidi Polanco (PUCV-EII) 02/08/2007 El “contexto” de la clase Punto no tiene visibilidad de cómo ésta almacena sus datos. Consecuencia: Puede modificarse el conjunto de variables de la clase Punto, sin que esto afecte su contexto. 28 Encapsulación en la Implementación de Métodos Ejemplo: Ejemplo public class Angle{ private double angle; public void setAngle(double a){ angle = a; } public double getSin(){ // Cálculo mediante series de // Taylor ... } } Franco Guidi Polanco (PUCV-EII) Encapsulación del Tipo El “contexto” de la clase Angle no tiene visibilidad del algoritmo de cálculo del seno del ángulo Figura Contexto Rombo Consecuencia: Puede modificarse la implementación del método sin afectar al contexto. 02/08/2007 +dibujar() +rellenar() +ocultar() 29 Franco Guidi Polanco (PUCV-EII) Encapsulación de Detalles de Diseño +agregar() +sacar() Franco Guidi Polanco (PUCV-EII) Cuadrado Círculo +dibujar() +rellenar() +ocultar() +dibujar() +rellenar() +ocultar() Consecuencia: El contexto puede implementar una lógica común para utilizar cualquiera de las figuras (o cualquier nueva subclase). 02/08/2007 30 Ejemplo Banco Cola +dibujar() +rellenar() +ocultar() Encapsulación de Reglas de Instanciación Ejemplo Contexto El “contexto” de las figuras no tiene visibilidad de cuál de ellas está exactamente utilizando. +recibirCliente() +atenderSiguiente() Cajero +atender() 02/08/2007 El “contexto” de la clase Banco no tiene visibilidad de las clases que soportan sus operaciones. Consecuencia: Puede modificarse la “arquitectura” del Banco sin afectar el contexto. 31 public class LeonardoDaVinci{ private static LeonardoDaVinci instance; private LeonardoDaVinci(){} public static LeonardoDaVinci getLeonardo(){ if( instance == null ) instance = new LeonardoDaVinci(); } return instance; } public String writeName(){ return “odranoeL”; } ... El “contexto” de la clase no tiene visibilidad de la lógica de instanciación de objetos LeonardoDaVinci. ¿Consecuencias? } Franco Guidi Polanco (PUCV-EII) 02/08/2007 32 Encapsulación y Diseño Encapsulación y Diseño Muchos patrones de diseño (soluciones a problemas comunes de diseño) utilizan la encapsulación de tipos para crear capas de separación entre objetos. Ante especificaciones variables y sistemas que evolucionan... ... ¿podemos lograr un buen diseño? La separación se crea asignando referencias a clases abstractas o interfaces. Sí, encontrando qué cosas pueden variar en el diseño y Franco Guidi Polanco (PUCV-EII) Esto permite modificar alguno de los “lados” de la capa de separación, sin afectar a la otra. encapsulándolas 02/08/2007 33 El principio “abierto-cerrado” Franco Guidi Polanco (PUCV-EII) 02/08/2007 34 El principio “abierto-cerrado” En el libro Object Oriented Software Construction de 1988, Bertrand Meyer propuso el “Open-Closed Principle” (OCP) Síntomas de un mal diseño (cuando no se cumple este principio): “Las entidades de software (clases, módulos, funciones, etc.) deberían estar abiertos para extensión y cerrados para modificaciones.” Por lo tanto: Al modificar un módulo de software, los cambios se propagan a otros módulos. Se deben diseñar módulos que nunca cambiarán. Si los requerimientos cambian, se debe extender el comportamiento de tales módulos, agregando nuevo código, no modificando aquél existente. Bertrand Meyer En otras palabras, el software debe ser diseñado para soportar la adición de nuevas funcionalidades, sin que esto comporte modificaciones en aquellas existentes. La base de este principio está en los conceptos de: Abstracción Polimorfismo No es siempre posible seguir completamente este principio. Franco Guidi Polanco (PUCV-EII) 02/08/2007 35 Franco Guidi Polanco (PUCV-EII) 02/08/2007 36 El principio “abierto-cerrado” El principio “abierto-cerrado” Los módulos desarrollados bajo este principio tienen dos características: Aquí no se respeta el principio: Personaje Están “abiertos para extensión”: el comportamiento del módulo puede ser extendido, a fin de lograr un nuevo comportamiento, impusto por nuevos requerimientos de la misma o de otra aplicación. Están “cerrados para modificaciones”: el código fuente de un módulo existente no debe ser alterado. public void pinta(Personaje p){ if( seRequierePintar ){ if( p instance of Héroe) pintaHeroe((Héroe) p); else if(p instance of Enemigo) pintaEnemigo((Enemigo) p); } … } Héroe Enemigo Dibujador pinta(Personaje p) pintaHéroe(Héroe p) pintaEnemigo(Enemigo p) ¿Qué pasa si es necesario agregar un nuevo tipo de personaje (ej. un Mago)? Franco Guidi Polanco (PUCV-EII) 02/08/2007 37 El principio “abierto-cerrado” Franco Guidi Polanco (PUCV-EII) 02/08/2007 38 Consecuencias del principio “abierto–cerrado” Aquí si es respetado el principio: “Regla” de diseño: Establecer visibilidad “privada” a variables de instancia. Personaje Dibujador Los conceptos de abstracción y polimorfismo del principio abierto-cerrado están asociados a la especificación de jerarquías de herencia. pinta() pinta(Personaje p) Héroe public void pinta(Personaje p){ if( seRequierePintar ) p.pinta(); … } pinta() Enemigo pinta() Es posible agregar nuevos objetos a pintar (agregando una subclase de Personaje), sin modificar el código ya existente Franco Guidi Polanco (PUCV-EII) 02/08/2007 39 ¿Hay algo que decir respecto de las jerarquías de herencia? (Veamos el principio de sustitución de Liskov) Franco Guidi Polanco (PUCV-EII) 02/08/2007 40 El principio de sustitución de Liskov El principio de sustitución de Liskov Barbara Liskov, en “Data Abstraction and Hierarchy”, SIGPLAN Notices, 23, 5 (May 1988) estableció lo que hoy se conoce como el “Liskov Substitution Principle” (LSP): Ejemplo: Programador “Las funciones que utilizan punteros o referencias a clases de base, deben ser capaces de utilizar subclases de éstas, sin necesidad de conocerlas” Vehículo Esto es: Cualquier propiedad que sea cierta para una súperclase, debe serlo también para sus subclases. Un cliente de una clase debe funcionar correctamente con cualquier subclase de ésta última. Franco Guidi Polanco (PUCV-EII) Bus Barbara Liskov 41 El principio de sustitución de Liskov Terrestre Tren Kandinsky Franco Guidi Polanco (PUCV-EII) 02/08/2007 42 “Los módulos de alto nivel no deberían depender de los módulos de bajo nivel. Ambos deberían depender de abstracciones. Las abstracciones no deberían depender de detalles. Los detalles deberían depender de abstracciones.” programa(vehiculo v) Bus 02/08/2007 El “Dependency Inversion Principle” (DIP) establece cómo implementar los objetivos enunciados por el OCP y el LSP. Vehículo public void programa(Vehículo v){ if( v instance of Bus ){ throw new InvalidException(); else programaAutomático(v); } } Franco Guidi Polanco (PUCV-EII) El principio de “inversión de dependencia” El LSP se violaría ante la presencia de situaciones como la siguiente: Aéreo Tren La clase Programador debe funcionar correctamente con la clase Vehículo, o con cualquier subclase de ella 02/08/2007 Programador Terrestre Aéreo 43 Franco Guidi Polanco (PUCV-EII) Klee 02/08/2007 44 El principio de “inversión de dependencia” El principio de “inversión de dependencia” Propone la estategia de depender de abstracciones (interfaces, funciones abstractas y/o clases abstractas), en vez de depender de funciones y clases concretas. Aquí no se cumple el DIP: public void copiar(LectorTeclado l, EscritorDisco e){ while( !l.eof){ byte b = l.leer(); e.escribir( b ); } } Copiador LectorTeclado Franco Guidi Polanco (PUCV-EII) 02/08/2007 45 El principio de “inversión de dependencia” Los objetos son inherentemente reactivos: su comportamiento es gatillado por la acción (externa) de otro objeto. Copiador Franco Guidi Polanco (PUCV-EII) 46 Los objetos ofrecen una interfaz pública por medio de la cual otros objetos invocan sus comportamientos. public void copiar(Lector l, Escritor e){ while( !l.eof){ byte b = l.leer(); e.escribir( b ); } } LectorTeclado 02/08/2007 Después de la OO... ¿qué? Aquí sí se cumple: Lector Franco Guidi Polanco (PUCV-EII) EscritorDisco La lógica general del copiador tiene “cableada” la acción sobre un LectorTeclado y un EscritorDisco ¿Posibilidad de reutilizar el código del Copiador? Nuevo paradigma: agentes de software. Los agentes de software son unidades de software con objetivos y responsabilidades. Escritor Los agentes de software exhiben comportamiento proactivo: un agente actúa por cuenta propia sobre el ambiente y sobre otros agentes para alcanzar sus objetivos. EscritorDisco 02/08/2007 47 Franco Guidi Polanco (PUCV-EII) 02/08/2007 48