- Fundamentos de la programación orientada a objetos: Abstracción
- Fundamentos de la programación orientada a objetos: Encapsulación
- Fundamentos de la programación orientada a objetos: Herencia
- Fundamentos de la programación orientada a objetos: Polimorfismo
- Fundamentos de la programación orientada a objetos: Composición (diseño)
- Definición de clases
- Derechos de acceso
- Constructores: por defecto, paramétricos y de copia
- Destructores
- Autorreferencia: esto
- Herencia
- Creación dinámica de objetos: nuevo y suprimir
- Punteros
- Métodos virtuales (virtual y override)
- Miembros estáticos
- Tipos anidados, espacios de nombres y operador de contexto '::'
- Dividir definición y declaración de clase
- Clases abstractas e interfaces
- Sobrecarga de operadores
- Conversión de tipos de objeto: dynamic_cast y puntero void *
- Punteros, referencias y const
- Gestión de la herencia: final y delete
Clases abstractas e interfaces
Para explorar las clases abstractas y las interfaces vamos a volver a nuestro ejemplo de programa de dibujo de extremo a extremo. Para simplificar, su API consiste en un único método draw virtual. Hasta ahora, ha estado vacía, pero al mismo tiempo, incluso una implementación tan vacía es una implementación concreta. Sin embargo, los objetos de la clase Shape no se pueden dibujar: su forma no está definida. Por lo tanto, tiene sentido hacer que el método draw sea abstracto o, como también se denomina, puramente virtual.
Para ello, hay que eliminar el bloque con una implementación vacía y añadir «= 0» a la cabecera del método:
class Shape
|
Una clase que tiene al menos un método abstracto también se convierte en abstracta, ya que su objeto no se puede crear: no hay implementación. En concreto, nuestro constructor Shape estaba disponible para las clases derivadas (gracias al modificador protected), y sus desarrolladores podían, hipotéticamente, crear un objeto Shape. Pero era así antes, y después de la declaración del método abstracto detuvimos este comportamiento, ya que lo habíamos prohibido nosotros, los autores de la interfaz de dibujo. El compilador arrojará un error:
'Shape' -cannot instantiate abstract class
|
El mejor enfoque para describir una interfaz es crear una clase abstracta para ella que contenga sólo métodos abstractos. En nuestro caso, el método draw debería trasladarse a la nueva clase Drawable, y la clase Shape debería heredarse de ella (Shapes.mq5).
class Drawable
|
Por supuesto, los métodos de interfaz deben estar en la sección public.
MQL5 proporciona otra manera conveniente de describir las interfaces mediante el uso de la palabra clave interface. Todos los métodos de una interfaz se declaran sin implementación y se consideran públicos y virtuales. La descripción de la interfaz Drawable que equivale a la clase anterior tiene el siguiente aspecto:
interface Drawable
|
En este caso no es necesario cambiar nada en las clases descendientes si no hubo campos en la clase abstracta (lo que supondría una violación del principio de abstracción).
Ahora es el momento de ampliar la interfaz y hacer que el trío de métodos setColor, moveX, moveYtambién formen parte de ella.
interface Drawable
|
Tenga en cuenta que los métodos devuelven un objeto Drawable porque no sé nada de Shape. En la clase Shape tenemos ya implementaciones que son adecuadas para sobrescribir (override) estos métodos, porque Shape hereda de Drawable (Shape «son una especie de» objetos Drawable).
Ahora, los desarrolladores externos pueden añadir otras familias de clases Drawable al programa de dibujo, en concreto, no sólo formas, sino también texto, mapas de bits y también, sorprendentemente, recopilaciones de otros Drawables, lo que permite anidar objetos unos dentro de otros y realizar composiciones complejas. Basta con heredar de la interfaz e implementar sus métodos.
class Text : public Drawable
|
Si las clases de forma se distribuyeran como una biblioteca binaria ex5 (sin códigos fuente), proporcionaríamos un archivo de cabecera que sólo contuviera la descripción de la interfaz y ninguna pista sobre las estructuras de datos internas.
Dado que las funciones virtuales se vinculan dinámicamente (más tarde) a un objeto durante la ejecución del programa, es posible obtener un error fatal «Llamada a función virtual pura»: el programa termina. Esto ocurre si el programador «olvidó» sin darse cuenta proporcionar una implementación. El compilador no siempre es capaz de detectar tales omisiones en tiempo de compilación.