La PDO tiene como característica más importante la abstracción. Los humanos solucionan los problemas complejos mediante la abstracción; un ejemplo: no se piensa en un ordenador como un conjunto de decenas de miles de piezas individuales. Más bien se concibe como un objeto con un comportamiento determinado y que se utiliza según unas necesidades, y para ello no se piensa en la relación que hay entre la fuente de alimentación, la placa base, el microprocesador, etc. Cuando se lee un fichero del disco duro, no es necesario saber la manera en que el sistema operativo accede a éste. Y dentro del sistema operativo no se conoce la manera que tiene el subsistema de ficheros de acceder al dispositivo de almacenamiento. Y por último, dentro del disco duro cómo las cabezas acceden a la pista, sector y cluster del fichero en concreto, etc.
Se puede aplicar esta misma filosofía a la construcción de programas y establecer una relación jerárquica entre un conjunto de objetos que realizan determinadas tareas a petición de otros objetos mediante mensajes.
Para llevar a cabo la abstracción que se ha visto anteriormente, es necesario entender algunos de los conceptos más relevantes de este paradigma, como son las clases, los objetos, etc.
Se puede entender un objeto como una entidad de programa que contiene datos y todos aquellos procedimientos que pueden manipular esos datos. Antes de crear un objeto hay que definir su estructura y su funcionalidad, esto es lo que se conoce como la clase del objeto. Por lo tanto, se puede definir una clase como una colección de objetos que poseen una características y operaciones comunes. Así pues todo objeto debe pertenecer a una clase. Las clases constituyen la base de la PDO, ya que constituyen las plantillas con las que se construirán los objetos.
La relación entre clase y objeto es equivalente, por ejemplo, a la relación entre el concepto de persona y una persona en concreto. El concepto de persona engloba un conjunto de propiedades comunes a todos los seres humanos (seres vivos, mamíferos, inteligentes, altura, peso, nombre...) y una serie de funciones que los caracterizan (nacer, morir, comer, respirar, pensar...). A partir de esta clase persona se pueden obtener individuos concretos (personas), conocidos como objetos o instancias de la clase persona. Cada objeto tendrá unos valores concretos para las propiedades anteriores (Nombre: Alvaro Altura: 1,70 m Peso: 76 Kg).
Los elementos (variables y funciones) que forman parte de una clase se denominan generalmente miembros de la clase. Las variables se conocen también por atributos o variables de clase, cuyos valores determinan el estado interno del objeto, mientras que las funciones se llaman métodos de la clase o, simplemente, métodos. Cada vez que se crea un objeto (es decir, cada vez que se instancia una clase), contendrá las variables (llamadas de instancia) y los métodos de la clase. Estos métodos podrán entre otras cosas consultar o modificar el estado interno del objeto.
Cuando un objeto necesita conocer o modificar el estado de otro le envía un mensaje que consiste en la ejecución del método correspondiente y en la posible devolución de un objeto que se considera respuesta.
Los métodos se pueden clasificar en tres categorías:
· Observadores: devuelven información acerca del estado del objeto.
· Modificadores: permiten que un objeto pase de un estado a otro.
· Constructores: permiten crear nuevos objetos.
Toda clase ha de tener, al menos, un método llamado constructor que sea el encargado de generar los nuevos objetos (reservar memoria, inicializar estado...). Este método, como cualquier otro, puede recibir parámetros pero no puede devolver ningún valor. Al declarar un objeto de una clase se llamará automáticamente al constructor adecuado. De igual forma, es muy habitual que antes de destruir el objeto se haga uso de otro método del objeto con el fin de liberar memoria, recursos, etc. Éste método se denomina destructor y a diferencia del anterior no puede ni recibir ni devolver parámetros.
Los métodos podrán ser definidos como públicos o privados; éstos últimos sólo pueden ser utilizados por la propia clase. Cualquier otro código que no es miembro de la clase no puede acceder a un método o variable privado, lo que asegura que no se van a producir accesos no permitidos. Al conjunto de los métodos públicos de la clase se le llama interfaz y permite la comunicación entre objetos. Existen ciertos lenguajes que no son tan estrictos respecto a este tema, puesto que dejan definir variables tanto privadas como públicas, es decir, accesibles directamente por los demás objetos. No obstante, esta práctica no es recomendable.
Para terminar, señalar el hecho de que todas las entidades son objetos, por tanto todas las clases del sistema deben ser objetos con propiedades comunes. Este conjunto puede ser definido por una "nueva clase" que describa propiedades comunes a todas ellas. Este concepto se denomina metaclase y tiene por instancias las otras clases del sistema. Así, se permite ver las clases como objetos.
El encapsulado es el mecanismo que permite mantener los datos y los métodos que manejan esos datos alejados de posibles usos indebidos. Se garantiza a través de la separación entre la interfaz de una clase y la estructura interna de los objetos e implementación de sus métodos.
La única forma de acceder a los objetos es a través de mensajes: nada saben otros objetos de sus variables de instancia (su representación) y del código de los métodos. Es decir, se puede modificar la implementación de dichos métodos (por ejemplo, para hacerlo más eficiente) sin variar su interfaz.
Para relacionar este concepto con el mundo real, considere la transmisión de un automóvil, que encapsula miles de bits acerca del motor, como cuánto está acelerando, el terreno sobre el que está y la posición de la palanca de cambios. Usted, como usuario, sólo tiene un método para actuar sobre este encapsulado complejo: mover la palanca de cambios. Por lo tanto, no puede afectar a la transmisión utilizando el intermitente o el limpiaparabrisas. La palanca de cambios es un interfaz bien definida para la transmisión. Es más, lo que ocurre dentro de la transmisión no afecta a los objetos que están fuera de ella, por ejemplo, al cambiar la marcha no se enciendan las luces. Como la transmisión automática está encapsulada, docenas de fabricantes de coches pueden implementar este mecanismo de la manera que les parezca bien. Sin embargo, desde el punto de vista del conductor todas funcionan igual.
Esta misma idea se puede aplicar a la programación. El poder del código encapsulado es que todo el mundo conoce cómo acceder a él y pueden utilizarlo independientemente de los detalles de implementación y sin temor a efectos colaterales inesperados.
Uno de los objetivos más importantes de la PDO es la reutilización de código. Al igual que en otras ingenierías se pueden reutilizar ciertos diseños, en programación ésta debería ser una actividad fácil de llevar a cabo. El mecanismo básico para la reutilización es la herencia. La herencia permite extender la funcionalidad de un objeto. Así podemos definir nuevos tipos restringiendo o aumentando propiedades del tipo original, generándose, de esta forma, una jerarquía de clases.
Así, las ventajas de la herencia son:
Ø Reutilización del software: cuando el comportamiento se hereda de otra clase, no necesitamos rescribir el código responsable de ese comportamiento. Además, aporta mayor confiabilidad: cuanto más utilicemos ese código mayores serán las posibilidades de encontrar errores.
Ø Compartición de código: muchos usuarios o proyectos independientes pueden hacer uso de las mismas clases. Podemos referirnos genéricamente a estas clases como componentes software. Otra forma de compartición de código ocurre cuando un programador construye dos o más clases diferentes que heredan de una clase paterna única.
Ø Consistencia de la interfaz: si tenemos un conjunto de clases que heredan de la misma superclase, se asegura que el comportamiento que heredan será similar en todos los casos. Así, es más fácil garantizar que las interfaces de objetos similares tengan comportamientos similares, y al programador se le hace más fácil recordar la manera en que éstos objetos se utilizan.
Ø Ocultación de información: cuando se desarrolla un componente software sólo se necesita entender la naturaleza del componente y su interfaz. Es decir, no importa comprender cómo se ha implementado dicho componente.
A la acción de crear una clase a partir de otra ya existente se le conoce como herencia o derivación. Al tipo original se le llama superclase o ascendente, mientras que a la nueva clase se le conoce como subclase, descendente o derivada. En muchas bibliografías se limitan a utilizar los términos clase padre o clase hija.
La herencia es una característica que cuenta con la propiedad transitiva. Por ejemplo, la clase Vehículo, tiene unas características, si después se crea una clase que hereda de ésta llamada Coche con sus características propias, heredará los miembros de la clase Vehículo. Si ahora se crea una nueva clase que heredara de Coche, por ejemplo la clase Deportivo, ésta heredaría los componentes de la clase Coche y por transitividad los de la clase Vehículo. Esta propiedad es muy importante por que es la que permitirá realizar jerarquías de clases de gran complejidad partiendo de elementos más o menos simples que se van heredando de una clase a otra.
No obstante existe la posibilidad de eliminar o modificar características heredadas en las subclases mediante anulación y redefinición respectivamente.
Como se ver en la figura, la clase padre sería Vehículo, de la que heredan sus características, la clase Coche, que a su vez puede introducir características nuevas. De Coche hereda Deportivo, la cual también puede introducir características nuevas, y que por ser también un coche hereda todos los atributos de Vehículo. Las características heredadas no habría que implementarlas otra vez debido a que en Java la herencia utiliza la reutilización.
En esta otra imagen se puede ver un ejemplo de cuatro objetos distintos que heredan de la clase Coche. De forma que se tiene una clase padre, Coche, y cuatro clases hijas, Turismo, Deportivo, Autobús y Ranchera.
Finalmente, se conoce como herencia múltiple a la posibilidad de que una clase herede de varios padres. Esta propiedad nos va a permitir modelizar los objetos del mundo real cuando se tienen varias visiones de los mismos. Por ejemplo, se puede tener una clase Cafetería, otra clase Sala de ordenadores, y por último, una clase que se llame Cibercafé que herede de las dos a la vez.
La herencia múltiple presenta un grave problema, que se presenta si varias clases padres tienen miembros con un mismo nombre. Como no todos los lenguajes de programación tienen un método para implementar la herencia múltiple existen varias maneras de evitarla reestructurando la jerarquía de clases.
El término polimorfismo abarca muchos aspectos, por lo que se ha hecho la siguiente clasificación, para facilitar su comprensión:
Hay que distinguir una variable, de su tipo y del valor que esta toma. Hay lenguajes en los que se asigna un tipo a una variable cuando se crea y su tipo no puede cambiar durante la ejecución del programa. El valor de la variable se toma en el dominio del tipo, o como mucho en dominios incluidos en el tipo (p.e integer y long).
int x=7.
Si el tipo puede cambiar durante la ejecución, se dice que el tipo es dinámico. p.e:
int x=7;
char x='a'.
Se dice en este caso que la variable es polimórfica, puede tomar varias formas o ser de diferentes tipos durante toda su vida.
Si en lugar de variables se trabaja con objetos y con clases en lugar de tipos, decimos que el objeto es polimórfico.
Para que se pueda dar polimorfismo, las variables y objetos deben almacenarse en la parte de la memoria que se corresponde con el heap y no en el stack. El heap permite que el espacio de memoria reservado para las variables pueda cambiar en tiempo de compilación y ejecución, que es lo que ocurre cuando su tipo o clase cambia, mientras que el stack no lo permite.
Cuando dos métodos se invocan con el mismo mensaje y los mismos argumentos (igual número de argumentos e igual tipo) se habla de sobrecarga de métodos. Si la especificación de los métodos coincide, se habla de polimorfismo. Luego el polimorfismo necesita de la sobrecarga. El polimorfismo de métodos está ligado al de objetos y suele darse cuando hay herencia. La idea es poder especializar el comportamiento de los objetos utilizando el mismo mensaje para invocar a un método de una clase y a los especializados de éste en sus superclases. De esta forma, si el objeto es especializa, su protocolo sigue siendo el mismo.
Un ejemplo sencillo es el de la clase "Número". Sus subclases son "Entero", "Real"... El método * (multiplicar) existe en todas ellas pero cambia su implementación. Dado cualquier número se sabe que entenderá el mensaje *.
Otro ejemplo más para aclarar el polimorfismo puede ser el siguiente:
1) Clase Persona. Tiene un método "canta" cuya especificación es entonar una canción y cuya implementación incluye la canción 'La la la la...'. Si se crea un objeto Alvaro de la clase Persona y se le envía el mensaje canta, responderá: 'La la la la...'.
2) Clase Ladrón. Hereda de la clase Persona (los ladrones son personas). Tiene un método canta cuya especificación es entonar una canción y cuya implementación incluye la canción: 'Yo no he sido'. Si se crea un objeto "Luis" de la clase Ladrón y se le envía el mensaje canta, responderá: 'Yo no he sido'
Objeto Luis de la clase Ladrón, s Luis le envío el mensaje canta y éste responde: 'Yo no he sido'.
En tiempo de ejecución Alvaro puede especializarse y cambiar de clase, convirtiéndose en un objeto de la clase Ladrón. Al enviar el mensaje canta a Alvaro, es otro método el que se ejecuta, el de la clase Ladrón, y no el que se ejecutó cuando era solamente una persona.
El polimorfismo de método ha evitado tener que cambiar el mensaje que se envía a Alvaro, y ha permitido que, dependiendo de la clase a la que pertenezca el objeto en cada momento, el método que se invoque sea diferente. Es muy útil cuando en tiempo de ejecución no se si Alvaro es una Persona o un Ladrón pero se que el programa funcionará y que cantará de una u otra manera.
Un ejemplo de sobrecarga sin polimorfismo es el que viene dado por el mensaje +. Hay lenguajes donde + se utiliza para sumar números y para concatenar cadenas de caracteres. La especificación es diferente, luego tenemos sobrecarga.
A continuación se muestra una tabla donde se muestran algunas de las características más relevantes de los lenguajes de programación. Se centra en las operaciones de construcción e inicialización tanto estática como dinámicamente.
|
CREACION |
INICIALIZACION |
||
ESTATICA |
DINAMICA |
ESTATICA |
DINAMICA |
|
JAVA |
Declaracion |
new |
???????? |
Constructor |
FORTRAN |
Declaracion |
---- |
Declaracion |
Asignación |
PASCAL |
Declaracion |
New |
---- |
Asignacion |
C |
Declaracion |
malloc |
---- |
Asignacion |
CLU |
Declar. punteros |
Operac. creacion |
---- |
Asignacion |
C++ |
Declaracion |
New |
Constructor |
Constructor |
TPascal OO |
id |
New |
---- |
Constructor |
Smalltalk |
---- |
Método de clase |
---- |
Método de instancia |
EIFFEL |
Declar. punteros |
Create |
---- |
Asignacion t.b. |