TEMA 4: El paradigma orientado a objetos.
Características básicas de los lenguajes orientados a objetos.
Contenidos
- 1. Presentación
- 2. Historia
- 3. Elementos básicos de la POO
- 3.1. Clases
- 3.2. Objetos
- 3.3. Características de un objeto
- 3.4. Tipos de operaciones sobre un objeto
- 3.5. ¿Qué es un Lenguaje Orientado a Objetos?
- 3.6. Herencia
- 3.7. Enlace Dinámico
- 3.8. Paso de mensajes
- 3.9. Encapsulación
- 3.10. C++ como lenguaje orientado a objetos.
- 3.11. Ejemplo de enlace dinámico en C++. Comparativa con C.
- 4. Aclaraciones
1. Presentación
- En este tema vamos a tratar de aclarar el significado del término Orientado a objetos.
- En realidad es un paradigma que busca dos beneficios muy
concretos aplicados al software que creamos:
- Reusabilidad
- Extensibilidad
- Aunque nosotros nos centraremos en la parte de programación, está también muy ligado a las fases de análisis y diseño.
- Nos referiremos al término Programación Orientada a Objetos por su abreviatura en castellano: POO - OOP en inglés -.
2. Historia
- Los distintos elementos individuales que posteriormente conformaron lo que se conoce como POO aparecen con el desarrollo del lenguaje Simula-67.
- Posteriormente se amplia con el desarrollo por parte de Xerox del lenguaje/entorno de computación SmallTalk junto con un hardware novedoso: mira este vídeo.
- Y estos otros donde se explica más detalladamente el funcionamiento de este novedoso interfaz de usuario.
- La idea clave detrás de la POO es la de poder simular en un computador de manera sencilla modelos de la realidad.
- Para ello usamos en nuestro programa los mismos términos que empleamos al describir la realidad.
- Es por eso que aparecen aplicados a los lenguajes de programación
palabras como :
- Clases, Objetos.
- Paso de mensajes.
- Herencia.
- Enlace dinámico, etc…
3. Elementos básicos de la POO
3.1. Clases
- Son la descripción de uno o más objetos en base a una serie de atributos y servicios.
- A estos atributos se les llama también ‘variables de instancia’ y ‘variables de clase’, mientras que a los servicios se les llama ‘métodos’.
- Una clase es la 'esencia' del objeto. En ocasiones se describe como un "conjunto de objetos que comparten una estructura y comportamiento comunes".
- Una clase debe tener una ‘parte’ no visible desde el exterior y una parte visible que es la encargada de acceder a esta parte no visible. En la parte no visible se suelen situar las variables de instancia y/o clase, mientras que en la visible se colocan los métodos de instancia y/o clase.
3.2. Objetos
- Los objetos en el mundo real son aquellas 'cosas'…
- Que son tangibles y/o visibles.
- Que pueden ser comprendidas mentalmente.
- A las que va dirigido el pensamiento o la acción.
- Mientras que cuando los vemos desde el prisma del desarrollo de
software, los objetos son:
- Todo aquello que modela algo real y por tanto ocupa un espacio y un tiempo.
- Aquellas entidades reales o abstractas con un papel bien definido en el dominio del problema.
- Toda entidad que en nuestro diseño de una aplicación presenta un estado, un comportamiento y una identidad propios.
3.3. Características de un objeto
- La identidad de un objeto es la propiedad que lo distingue de otros, incluso pertenecientes a su misma clase. Suele ser un nombre único de variable. Es distinta a la igualdad.
- El estado de un objeto representa todas las propiedades, normalmente estáticas, del objeto además de los valores actuales, normalmente dinámicos, de cada una de estas propiedades.
- El comportamiento de un objeto es el modo en que éste actúa y reacciona (ante mensajes recibidos), hablando en términos de cambios en su estado y envío de mensajes a otros objetos.
- Los mensajes son las acciones que un objeto realiza sobre otro. A la manera concreta que un objeto responde a un mensaje enviado por otro, se le llama método.
3.4. Tipos de operaciones sobre un objeto
- Modificadoras (setters )
- Selectoras (getters )
- Iteradoras
- Constructoras
- Destructoras
- Estas operaciones pueden formar parte de una clase o estar fuera de cualquiera de ellas.
- Si una de ellas pertenece a una clase se le llama método, mientras que si no lo hace se le llama simplemente Función o también subprograma o función libre.
- Al conjunto de métodos y ‘subprogramas libres’ asociados con un objeto se le llama protocolo.
3.5. ¿Qué es un Lenguaje Orientado a Objetos?
Aquel que dispone de las siguientes características:
- Herencia
- Enlace Dinámico
- Paso de Mensajes
- Encapsulación
3.6. Herencia
- Mecanismo que permite expresar la similitud entre clases.
- ¿Cómo?: Podemos crear nuevas clases a partir de otra u otras ya existentes.
- A la clase nueva creada se le llama clase derivada y a la clase de la cual heredamos se le llama clase base. También se les suele llamar subclase y superclase respectivamente.
- De este modo incorporamos la estructura y el comportamiento de la clase pre-existente a la nueva que estamos creando.
- Dicho de otro modo: la clase derivada ‘comparte’ las variables de clase y de instancia, así como los métodos de clase y de instancia de su(s) superclase(s).
- La herencia reduce el número de cosas que hemos de ‘decir’ sobre una nueva clase que creamos si tenemos la precaución de hacer que herede de una clase parecida a ella.
- Las variables declaradas en una clase que se duplican o son propias de cada objeto creado de esa clase se llaman variables de instancia.
- Las variables declaradas en una clase que se comparten por todos los objetos de una misma clase se llaman variables de clase.
- Los métodos declarados en una clase que pueden acceder a las variables de instancia de un objeto se llaman métodos de instancia.
- Los métodos declarados en una clase que pueden acceder a las variables de clase de una clase se llaman métodos de clase.
- Tendremos herencia simple si heredamos sólo de una clase y herencia múltiple si heredamos de más de una.
- La herencia múltiple puede plantear problemas como el de la herencia repetida. C++ es uno de los pocos LOO que soporta herencia múltiple de clases.
- Los lenguajes que no permiten herencia múltiple de clases introducen un concepto nuevo, el interfaz (no confundir con el interfaz de una clase).
- Un interfaz es similar a una clase, pero no tiene datos, sólo métodos. Generalmente estos métodos son abstractos.
- La relación de herencia se debe utilizar (junto con otras) para reflejar relaciones entre objetos del mundo real de la manera más fiel posible.
- Los tipos de relaciones más habituales que podemos encontrar entre
objetos en el mundo real son:
- Es un (IsA) : Un robot es un autómata.
- Tiene un (HasA) : Un robot tiene un sensor.
- Usa un (Uses) : Un robot usa un cargador de baterías.
- La relación Es un introduce el principio de sustitución.
- La relación Tiene un introduce el concepto de composición entre objetos.
- Los métodos heredados se pueden usar directamente o también se pueden reescribir en la clase derivada.
- En este caso podemos hacerlo de dos modos:
- Reemplazándolos completamente.
- Refinándolos, para ello en algún punto del método reescrito en la clase derivada invocamos al método heredado de la clase base.
- Es posible que a un método de una clase no tenga sentido proporcionarle una implementación. En este caso se puede dejar sin ella, pero convenientemente anotado, y se le llama método abstracto.
- Cuando una clase tiene al menos un método abstracto automáticamente pasa a ser una clase abstracta.
- Una clase abstracta no puede tener instancias.
3.7. Enlace Dinámico
- En programación I os han explicado lo que es el enlazador: ld.
¿Qué es entonces el tiempo de enlace?:
Es el instante de tiempo en el que se determina o identifica el trozo de código, p.e. una función que ha de ser llamada tras el envío de un mensaje, o el significado de una ‘construcción’ especial en memoria, p.e. el tipo exacto de una variable o un dato.
- En POO existen al menos dos atributos que se ven directamente
afectados por el tiempo de enlace:
- El tipo del dato al que se refiere un identificador.
- El método con el que se responde a un mensaje.
- Para hablar sobre el tipo de un identificador debemos distinguir
entre:
- Identificador: Es sólo un nombre.
- Valor: El contenido de la memoria asociada a un identificador.
- Tipo: Depende del lenguaje con el que trabajemos. En unos casos irá asociado a una variable y significará una cosa, mientras que en otros irá asociado a un valor y significará otra.
- Los lenguajes en los que el tipo de toda expresión se conoce en tiempo de compilación se llaman fuertemente tipados (LFT).
- En los LFT, p.e. el concepto de tipo va ligado al de
variable, es decir, los tipos se asocian con un identificador
mediante sentencias explícitas:
int n;
- En los LFT el tipo sirve para dar idea de los posibles valores que puede tomar una variable.
- C++-11 permite declarar variables de tipo
auto
, en las cuales el compilador infiere el tipo a partir de la expresión de inicialización de la variable:auto n = 3;
- Por completitud, los lenguajes donde el tipo no se asocia a un identificador sino a un valor, se llaman débilmente tipados (LDT).
- Ejemplos de lenguajes
- débilmente tipados:
python
,smalltalk
,perl
, etc… - fuertemente tipados:
C
,C++
,Java
,C#
,D
, etc…
- débilmente tipados:
Si retomamos el concepto del principio de sustitución y una declaración de clases con herencia como esta:
class Automaton { public: void compute () {...} }; class Robot : public Automaton {...}; // Es Un // Un robot es un automata.
El principio de sustitución nos dice que en cualquier lugar de nuestro código donde podamos usar un dato de clase o tipo
Automata
, podremos usar uno de claseRobot
, p.e.:Robot* r = new Robot; r->compute (); Automaton* aa[100]; aa[0] = r; aa[1] = new Automaton;
- La decisión del tipo de objeto con el que se va a trabajar no se puede saber hasta el mismo instante de la ejecución.
- A esto es a lo que llamamos enlace dinámico.
Pero el enlace dinámico no afecta solo a la decisión sobre el tipo real del objeto al que se refiere una variable, sino que esto tiene consecuencias, p.e., con el código a invocar (método) en respuesta a un mensaje recibido.
class Automaton { public: virtual void compute () {...} // compute tiene ahora enlace dinámico (virtual) }; class Robot : public Automaton { void compute () {...} // Se redefine en clases derivadas // no es necesario virtual, ya se sabe. }; class ChessPlayer : public Automaton { void compute () {...} };
¿Que crees que pasará con este código?
Automaton* aa[10]; aa[0] = new Automaton; aa[1] = new ChessPlayer; aa[2] = new Robot; aa[3] = new Automaton; ... for (auto ai = 0; ai < 10; ai++) aa[ai]->compute();
- ¿Y si es el usuario el que elige el tipo de automáta en tiempo de ejecución desde un menú?
- ¿Si tan bueno es el enlace dinámico por qué no está habilitado por defecto en C++?
- De hecho algunos lenguajes orientados a objetos sí que lo hacen: Smalltalk, Java, C#, D, etc…
- La respuesta está en la eficiencia en tiempo de ejecución. Piensa a qué puede deberse esto.
3.8. Paso de mensajes
- Cuando trabajamos con un objeto instancia de una clase que tiene una parte visible y otra no-visible desde el exterior de la misma, las funciones normales (externas a una clase) no tienen capacidad para acceder a la parte no-visible de la clase.
- Sólo las operaciones definidas dentro de la clase pueden acceder a las variables de instancia o de clase.
- Por tanto debemos invocar una de estas operaciones definidas en la clase
sobre un determinado objeto:
obj.operation()
(envío del mensaje). - C++ permite levantar esta restricción mediante las llamadas funciones amigas. En otros lenguajes se puede hacer de otras formas.
- Tradicionalmente en la POO se denomina a esta acción enviar un
mensaje a un objeto, p.e.:
robot.avanzarLineaRecta(20);
- Del mismo modo, el código que ejecuta el objeto en respuesta a este mensaje se le llama método.
- Un mensaje no es exactamente igual a una función libre:
- Siempre tendrá un parámetro más. Normalmente es un parámetro oculto.
- Si el nombre de una función tiene una relación 1:1 con el código a ejecutar al ser llamada, en el caso de un mensaje esta relación es 1:N. Piensa a qué puede ser debido esto.
- En C++ este parámetro oculto se llama this.
3.9. Encapsulación
- Es la característica que nos permite agrupar bajo una misma entidad los datos y las funciones que trabajan con esos datos.
- Es un mecanismo más de abstracción.
Hasta ahora conoceis dos:- División en funciones
- Tipos Abstractos de Datos.
- Permite establecer zonas de visibilidad desde el exterior al interior de esta entidad.
- ¿Qué ganamos al unir bajo una misma entidad datos y funciones?:
- La independencia del código que usa esta entidad de los cambios que pueda haber en su representación interna.
- Normalmente los datos suelen estar en la parte no-visible mientras que las funciones o métodos están en la parte visible. De este modo reforzamos el punto anterior.
- A esta parte visible se le conoce como el interfaz de la clase o también parte pública. A la parte no-visible se le llama implementación o también parte privada.
- En los LOO la entidad que aporta la capacidad de encapsulación es la clase.
- En los LOO a una variable cuyo tipo sea una clase se le llama objeto. También nos podemos referir a ella como una instancia de la clase o simplemente instancia.
- A las funciones definidas dentro de una clase se les llama métodos.
- Al igual que con las variables, existen métodos de clase y métodos de instancia.
- Un método de instancia definido en una clase se invoca a través
del operador "\(\bullet\)", o del operador "\(\to\)" (si la variable
que representa al objeto es un puntero):
object.method (parameters)
objectPtr->method (parameters)
- En lenguajes como
Java
,C#
,D
, etc… sólo se emplea la formaobject.method (parameters)
. - En el caso de un método de clase, si, p.e., el identificador
DashBoard
representa un nombre de una clase, usar un método de clase con ese identificador lo haríamos así:DashBoard::classMethod (parameters)
3.10. C++ como lenguaje orientado a objetos.
- C++ es un LOO, es decir, soporta:
- Herencia
- Enlace Dinámico
- Paso de Mensajes
- Encapsulación
- Entre los temas del 5 al 9 iremos estudiando en detalle estos
conceptos y algún que otro más (excepciones, genericidad), así
como de que manera se incorporan en C++:
- Tema 5: Clases y objetos.
- Tema 7: Herencia, polimorfismo y enlace dinámico.
- Tema 8: Genericidad.
- Tema 9: Excepciones.
3.11. Ejemplo de enlace dinámico en C++. Comparativa con C.
3.11.1. Código C++
// Copyright (C) 2020-2023 Programacion-II // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. #include <iostream> #include <cstdint> class FiguraGeometrica { public: FiguraGeometrica() { // std::cout << "Hola, soy una FG tambien\n"; inc_count(); } virtual ~FiguraGeometrica() { std::cout << "FiguraGeometrica::DESTRUCTOR\n"; } // Prueba a usar esta definición de dibujar en lugar de la que hay // mas adelante. Si se produce algún error de compilación trata de // resolverlo. // virtual void dibujar() = 0; // FiguraGeometrica::dibujar tiene enlace dinámico virtual void dibujar() { std::cout << "FiguraGeometrica::dibujar\n"; }; static uint32_t get_count() { return count; } static void inc_count() { count++; } private: //static uint32_t count; // Probar a inicializar aqui. // O de este otro mode en C++17 o superior // inline static uint32_t count = 0; }; uint32_t FiguraGeometrica::count = 0; using FiguraGeometricaPtr = FiguraGeometrica*; class Circulo : public FiguraGeometrica { public: Circulo() { std::cout << "Hola, soy un circulo\n"; } virtual ~Circulo() { std::cout << "Circulo::DESTRUCTOR\n"; } void dibujar() { std::cout << "Circulo::dibujar\n"; } }; class Rectangulo : public FiguraGeometrica { public: Rectangulo() { std::cout << "Hola, soy un rectangulo\n"; }; virtual ~Rectangulo() { std::cout << "Rectangulo::DESTRUCTOR\n"; } void dibujar() { std::cout << "Rectangulo::dibujar\n"; } }; // Comprueba lo sencillo que es añadir una nueva clase al // diseño. Comparalo con lo que tendrias que hacer para añadir una // nueva clase en la versión de C no orientada a objetos. // class Triangulo : public FiguraGeometrica { // public: // Triangulo() { // std::cout << "Hola, soy un triangulo\n"; // }; // virtual ~Triangulo() { // std::cout << "Triangulo::DESTRUCTOR\n"; // } // void dibujar() { // std::cout << "Triangulo::dibujar\n"; // } // }; int main () { FiguraGeometricaPtr vfg[5] = {nullptr,nullptr,nullptr,nullptr,nullptr}; vfg[0] = new Circulo; vfg[1] = new Circulo; vfg[2] = new Rectangulo; vfg[3] = new Rectangulo; // En vfg[4] guardamos una FiguraGeometrica o un Triangulo. vfg[4] = new FiguraGeometrica; // vfg[4] = new Triangulo; for (int f = 0; f < 5; f++) { std::cout << "vfg[" << f << "]: "; vfg[f]->dibujar(); } for (int f = 0; f < 5; f++) { delete vfg[f]; } std::cout << "total de FG creadas: " << FiguraGeometrica::get_count() << '\n'; return 0; }
3.11.2. Código C
/* * Copyright (C) 2020-2022 Programacion-II * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include <stdio.h> #include <stdlib.h> /* * Trata de añadir una 'clase' Triangulo a este ejemplo. Compara todos * los pasos a realizar con los que tendrias que hacer en la versión * orientada a objetos en C++ */ enum TipoFigura { FG, CIRCULO, RECTANGULO }; typedef struct FiguraGeometrica { enum TipoFigura t; } FiguraGeometrica; typedef struct Circulo { enum TipoFigura t; } Circulo; typedef struct Rectangulo { enum TipoFigura t; } Rectangulo; typedef FiguraGeometrica* FiguraGeometricaPtr; typedef Circulo* CirculoPtr; typedef Rectangulo* RectanguloPtr; void FiguraGeometrica_dibujar(FiguraGeometricaPtr this) { printf("FiguraGeometrica::dibujar\n"); } void Circulo_dibujar(CirculoPtr this) { printf("Circulo::dibujar\n"); } void Rectangulo_dibujar(RectanguloPtr this) { printf("Rectangulo::dibujar\n"); } int main(int argc, char *argv[]) { FiguraGeometricaPtr vfg[5] = {NULL, NULL, NULL, NULL, NULL}; vfg[0] = malloc (sizeof (Circulo)); vfg[0]->t = CIRCULO; vfg[1] = malloc (sizeof (Circulo)); vfg[1]->t = CIRCULO; vfg[2] = malloc (sizeof (Rectangulo)); vfg[2]->t = RECTANGULO; vfg[3] = malloc (sizeof (Rectangulo)); vfg[3]->t = RECTANGULO; vfg[4] = malloc (sizeof (Circulo)); vfg[4]->t = CIRCULO; for (int f = 0; f < 5; f++) { switch (vfg[f]->t) { case FG: FiguraGeometrica_dibujar(vfg[f]); break; case CIRCULO: Circulo_dibujar((CirculoPtr) vfg[f]); break; case RECTANGULO: Rectangulo_dibujar((RectanguloPtr) vfg[f]); break; } } for (int f = 0; f < 5; f++) { free(vfg[f]); } return 0; }
4. Aclaraciones
- En ningún caso estas transparencias son la bibliografía de la asignatura, por lo tanto debes estudiar, aclarar y ampliar los conceptos que en ellas encuentres empleando los enlaces web y bibliografía recomendada que puedes consultar en la página web de la ficha de la asignatura y en la web propia de la asignatura.