PONTIFICIA UNIVERSIDAD CATÓLICA DE CHILE ESCUELA DE INGENIERÍA DEPARTAMENTO DE CIENCIA DE LA COMPUTACIÓN IIC 1102 INTRODUCCIÓN A LA PROGRAMACIÓN Profesor: Rodrigo Sandoval U.
Capítulo III – Programación Orientada a Objetos
1
PROGRAMACIÓN ORIENTADA A OBJETOS............................................................................................. 1 1.1 CONCEPTOS EN ORIENTACIÓN A OBJETOS ...................................................................................................... 1 1.1.1 Clase....................................................................................................................................................... 1 1.1.2 Objeto..................................................................................................................................................... 1 1.1.3 Relaciones .............................................................................................................................................. 2 1.1.4 Herencia................................................................................................................................................. 3 1.1.5 Polimorfismo .......................................................................................................................................... 3 1.2 LENGUAJES ORIENTADOS A OBJETOS ............................................................................................................. 4 1.3 EJEMPLO: VEHÍCULOS – HERENCIA Y POLIMORFISMO .................................................................................... 5
Material preparado por Rodrigo Sandoval U en Marzo y Agosto 2004
Introducción a la Programación Orientada a Objetos
Rodrigo Sandoval U.
1 Programación Orientada a Objetos Un enfoque más reciente (años ‘90) para la resolución de problemas, se basa en la utilización de “Objetos”, estableciendo un esquema en que cada elemento del mundo real se representa con mayor precisión y fidelidad, logrando así soluciones modulares (compuestas por partes, hasta cierto punto, independientes, que potencialmente pueden reutilizarse en otros modelos) y con encapsulación (que logra ocultar la complejidad de soluciones en ciertos módulos, evitando que los participantes de la solución tengan que dominar cada parte de ésta). La interacción entre los objetos se conceptualiza como el paso de mensajes (llamados a los métodos), que hacen que estos objetos reaccionen, cambiando su estado y condiciones. Una de las principales razones que avalan la programación OO, es la mantenibilidad del software desarrollado bajo este esquema. Su necesidad se fue viendo cada vez más evidente a medida que programas de cientos de líneas de código se ponían en producción y cualquier modificación mayor significaba un proceso traumático de revisión, entendimiento, modificaciones y pruebas. El enfoque OO nació en parte de la idea de resolver esta problemática, aislando la complejidad en módulos hasta cierto punto independientes, que son individualmente más fáciles de mantener.
1.1
Conceptos en Orientación a Objetos
En este contexto se definen los “objetos” como estructuras de software para representar elementos del mundo real, relacionándolos a través de una descripción del estado del objeto mediante variables o propiedades, y su comportamiento mediante métodos o funciones.
1.1.1 Clase Una clase es un “Tipo de Objeto”, definiendo todas aquellas características: campos o atributos y métodos o acciones, comunes a todos los objetos de este tipo. Cada clase, por ende, se define en base a elementos que la componen, que en definitiva establecerán la diferencia entre un objeto y otro. Los atributos o campos, son elementos que pueden ir tomando diferentes valores a lo largo de la vida del objeto, aún cuando algunos de éstos puedan ser definidos como constantes. Los métodos hacen uso de los atributos de la clase, así como de otros elementos propios, para determinar una secuencia de instrucciones descrita en forma de algoritmo. Adicionalmente, es factible definir la accesibilidad que se tendrá desde otros objetos a los elementos de dicha clase. Para ello se establece que éstos pueden ser públicos (visibles) o privados (sólo visibles desde los métodos componentes de la clase).
1.1.2 Objeto En una implementación OOP (Object Oriented Programming), se llaman “Instancias” a todas aquellos objetos distintos, de una misma clase, que se usen en un momento determinado. Cada objeto se identifica individualmente por su nombre (nombre de la instancia) y se define un estado representado por los valores de sus atributos en un momento particular del tiempo.
IIC 1102
Página: 1
Introducción a la Programación Orientada a Objetos
Rodrigo Sandoval U.
Diagrama que ilustra un programa compuesto de 4 objetos diferentes, los cuales interactúan entre ellos, como lo indican las flechas.
1.1.3 Relaciones La relación entre los objetos puede ir más allá que un simple esquema de mensajes entre ellos. Existen relaciones más poderosas, que se define como “Tipo de”, “Es un”, “Parte de”, “Tiene un”. Por ejemplo, hablando de figuras geométricas, se puede definir la clase Punto, con coordenadas x,y. Esto, en un pseudo-lenguaje OO se ve de la siguiente manera: Clase Punto { atributos: int x, y metodos: setX(int nuevoX) getX() setY(int nuevoY) getY() }
Evolucionando la idea un poco más allá, se pueden definir figuras geométricas en base a la definición de punto, como sería el caso de un círculo, que tiene un centro (un punto) y un radio. Clase Circulo { atributos: int x, y, radio metodos: setX(int nuevoX) getX() setY(int nuevoY) getY() setRadio(nuevoRadio) getRadio() }
IIC 1102
Página: 2
Introducción a la Programación Orientada a Objetos
Rodrigo Sandoval U.
En este caso, ambas clases tienen dos atributos comunes: x, y. Ambas ofrecen prácticamente los mismos métodos, y es la clase “Circulo” la que define un nuevo elemento y métodos asociados. Por ello, se puede definir que “Circulo es un Tipo de Punto”, aun cuando es más especializado que el Punto en si. Si declaramos una instancia de tipo Circulo, nos encontraremos con que esta instancia tiene los mismos elementos que un Punto, por lo que se asume que “Circulo Es un Punto” también. Declarando una tercera clase: Clase Figura { atributos: Circulo circulo1 Triangulo triangulo1 metodos: set(Punto donde) } Nos encontramos con que en el caso de Circulo y Triangulo (una clase definida equivalentemente a Circulo), ambas son “Parte de Figura”, y que a su vez, Figura “Tiene un Circulo y un Triangulo”.
1.1.4 Herencia La herencia nos permite establecer relaciones “Es un Tipo de” y “Es un” entre clases. En el ejemplo del punto, la declaración del Circulo en base a la declaración previa de Punto se vería:
Clase Circulo heredando de Punto { atributos: int radio metodos: setRadio(nuevoRadio) getRadio() }
De esta manera, esta relación de herencia asume que todo lo que sea válido en Punto, lo será también en Círculo. Por ello, un Circulo tendría como atributos el radio, y también, x e y, al igual que los métodos respectivos. De esa manera, la herencia se define como el mecanismo que permite que una clase A herede propiedades de una clase B. Los objetos de la clase A (llamada “Clase Derivada” o “Subclase”) tienen acceso a atributos y métodos de la clase B (llamada “Super Clase”), sin necesidad de volver a definirlos.
1.1.5 Polimorfismo La definición de polimorfismo en este contexto se basa en la posibilidad de utilizaar un mismo nombre para diferentes propósitos, que aunque relacionados, son ligeramente distintos. La idea del polimorfismo es permitir el uso de un nombre en una clase específica de acción. Dependiendo de qué tipo de datos se esté utilizando para trabajar, una instancia específica del caso general se ejecuta. Hay diversas formas de establecer polimorfismo con los lenguajes orientados a objeto: Reutilización de nombres de atributos y métodos, y la sobrecarga de métodos.
IIC 1102
Página: 3
Introducción a la Programación Orientada a Objetos
1.1.5.1
Rodrigo Sandoval U.
Reutilización de Nombres
En el ejemplo anterior del Punto, se podría definir el método Mover(x,y), que mueve el punto a una nueva dirección x,y. Esta misma función podría ser implementada en un objeto Triangulo, donde la función sería exactamente la misma – desplazar la figura a una nueva posición – pero actuaría de una manera distinta, ya que el objeto en sí es distinto, aún cuando el mismo nombre de método (Mover) sea utilizado. De esa manera, si el nombre del método resulta ser lo suficientemente explícito para representar la operación lógica que ejecuta, este nombre se puede reutilizar en la definición de otras clases, incluso en casos en que una clase que implementa el método, herede de otra clase que también lo implementa, de esa manera aprovechando el nombre, pero estableciendo una nueva versión de su funcionamiento. 1.1.5.2
Sobrecarga de Métodos
Cuando el mismo nombre de un método se utiliza más de una vez dentro de una misma clase, usualmente se diferencia por los parámetros que recibe. Tal es el caso de la declaración de constructores de las clases, donde pueden existir diversas versiones de éstos, cada uno diferenciándose del otro por los parámetros (el tipo de los parámetros, la cantidad, y el orden en que se entregan).
1.2
Lenguajes Orientados a Objetos
Según varios autores, el interés por la OO surgió en el contexto de la crisis del software de los años 70 (la falta de reusabilidad de software). Al hablar de la OO, se suelen identificar las siguientes ventajas: •
Desarrollo rápido de sistemas.
•
Mejora en la calidad y legibilidad del código.
•
Facilidad de mantenimiento.
•
Aprovechamiento del poder de los LOOP.
•
Reusabilidad de software y diseños.
•
Producción de sistemas más resistentes al cambio.
La OOP y los LOOP juegan un papel importante dentro de las tecnologías OO. Según la literatura, el término “objeto” emergió paralelamente en varios campos de la Informática a principios de los años 70, para hacer referencia a nociones superficialmente distintas aunque relacionadas. La identificación de la importancia de la composición de sistemas en niveles de abstracción, la ocultación de información y el desarrollo de mecanismos de tipos de datos abstractos en los años 70 tuvieron una gran influencia en el desarrollo de la OOP, aunque existe cierta polémica sobre como exactamente estos avances dieron lugar a lo que hoy en día se considera como OOP. El LOOP Simula apareció en 1962 (y más tarde Simula67 en 1967) y, aunque no fue muy utilizado, ha sido reconocido como el primer LOOP, incorporando los conceptos de clase y objeto. El concepto de OOP propiamente dicho fue presentado por Alan Kay, uno de los inventores de Smalltalk (el primer LOOP popular), algunos años más tarde: •
Todo es un objeto que almacena datos y al que se le puede hacer peticiones.
•
Un programa es un conjunto de objetos que intercambian mensajes.
•
Cada objeto tiene su propia memoria que está compuesta por otros objetos.
•
Cada objeto tiene un tipo de mensajes que puede recibir y procesar.
•
Todos los objetos de un cierto tipo pueden procesar los mismos mensajes.
Se trata de una caracterización muy general que no se puede aplicar a muchos de los LOOP más utilizados hoy en día. Smalltalk tenía estas características y fue concebido con el objetivo de ser un LOOP dinámico, que permitiera la adición de nuevas clases, objetos y comportamiento sobre la marcha.
IIC 1102
Página: 4
Introducción a la Programación Orientada a Objetos
Rodrigo Sandoval U.
En las actas del congreso HOPL II editadas en 1993 por la ACM (Association of Computing Machinery), Kay definió la OOP en términos de una célula que permite el flujo de información en las dos direcciones, pero en la cual lo que está dentro está oculto desde fuera. En 1985 apareció el LOOP Eiffel, diseñado para mejorar la productividad y calidad de programas OO, pero no fue muy utilizado. Para que la OOP se estableciera como un paradigma era necesario que los programadores lo adoptaran. Por eso fue muy efectiva la modificación de un LP ya existente para incorporar los conceptos (y beneficios) de la OOP, sin perder la posibilidad de reutilizar código fuente, como ocurrió con C++ (que es una extensión de C que incluye los conceptos OO). Otros LP han sido expandidos también para incorporar estos conceptos: Modula2 se convirtió en Modula3, Ada en Ada95, Lisp en CLOS (Common Lisp Object System) – vía Flavors, COBOL en Object COBOL, etc. Como ejemplos de LOOP de nueva creación se pueden destacar Python, Java y C#. Actualmente se pueden identificar unos 140 LOOP que se usan de alguna forma u otra. Otra manera de ver la OOP es como la evolución natural de la programación imperativa, desde la programación sin estructura, pasando por la programación procedural y modular. En primer lugar, la programación sin estructura es la más sencilla y cada programa consiste en una secuencia de instrucciones que operan sobre datos globales o comunes a todas las partes del programa. Lo que ocurre es que, según va creciendo el programa, van surgiendo problemas. Por ejemplo, si se necesita la misma secuencia de instrucciones en varias partes del programa, hay que copiarla. Para evitar este problema, se empezaron a extraer estas secuencias, a darles un nombre, y a ofrecer una técnica para llamarlas y devolver el flujo de control desde ellas al programa principal junto con los resultados. Así aparecieron los procedimientos y funciones y la programación procedural. Con la incorporación del paso de parámetros y procedimientos dentro de otros, se podían escribir programas con más estructura y menos probabilidades de errores. Así, en vez de ver un programa como una secuencia de instrucciones, se podía contemplar como una secuencia de llamadas a procedimientos. La extensión natural de este tipo de programación consistió en agrupar en módulos procedimientos comunes a varios programas, y así surgió la programación modular. En ella cada módulo tiene sus propios datos y estado, que se modifican con las llamadas al módulo. Por último, como se verá a continuación, la OOP soluciona algunos de los problemas de la programación modular: puede haber simultáneamente múltiples versiones de un mismo objeto y cada una es responsable de su propia creación y destrucción.
1.3
Ejemplo: Vehículos – Herencia y Polimorfismo
Se da mucho en casos en que se requiere representar modelos “jerárquicos” derivados del mundo real, en que los objetos involucrados tienen una relación de origen entre ellos. Tal es el caso en este ejemplo, donde se definen tres clases, dos de las cuales se podrían definir como “casos particulares” de la clase principal, o más bien “clases derivadas” de la superclase. public class Vehiculo { public int ruedas; // Cantidad de ruedas public int puertas; // Cantidad de puertas // Métodos .... public void Avanzar() { ... } }
IIC 1102
Página: 5
Introducción a la Programación Orientada a Objetos
Rodrigo Sandoval U.
public class Camion : Vehiculo {
// Herencia explícitamente declarada
public int tara; public int carga; // Métodos .... public void Avanzar() { ... } } public class Moto : Vehiculo {
// Herencia explícitamente declarada
public int cilindraje; // Métodos .... public void Avanzar() { ... } }
En el ejemplo anterior se puede ver que se implementa: -
Herencia de clases. Al declarar la clase Camion, y también la clase Moto, como derivaciones de una misma clase base: Vehiculo. Es importante recordar que esta herencia impone que tanto el Camion como la Moto son Vehiculo.
-
Polimorfismo. Tanto la clase base, como sus derivadas implementan un método de nombre Avanzar(). Este método en forma lógica tiene posiblemente el mismo significado en los tres casos, sin embargo, al estar definido en tres clases distintas, cada una de ellas puede definir su propia implementación, haciendo que su funcionamiento detallado difiera en todos los casos. Llama particularmente la atención que al re-definir el método Avanzar() de la clase base, en las clases derivadas, se reemplaza el funcionamiento de esta clase original.
IIC 1102
Página: 6