Qué es la abstracción, dentro del paradigma de la Programación Orientada a Objetos. En qué situaciones debemos aplicar el concepto de abstracción en nuestros programas.
Abstracción es un término del mundo real que podemos aplicar tal cual lo entendemos en el mundo de la Programación Orientada a Objetos. Algo abstracto es algo que está en el universo de las ideas, los pensamientos, pero que no se puede concretar en algo material, que se pueda tocar.
Pues bien, una clase abstracta es aquella sobre la que no podemos crear especímenes concretos, en la jerga de POO es aquella sobre la que no podemos instanciar objetos. Ahora bien, ¿cuál es el motivo de la existencia de clases abstractas? o dicho de otra manera, ¿por qué voy a necesitar alguna vez declarar una clase como abstracta?, ¿en qué casos debemos aplicarlas? Esto es todo lo que pretendemos explicar en este artículo.
Abstracción en el mundo real
La programación orientada a objetos sabemos que, de alguna manera, trata de "modelizar" los elementos del mundo real. En el mundo en el que vivimos existe un universo de objetos que colaboran entre sí para realizar tareas de los sistemas. Llevado al entorno de la programación, también debemos programar una serie de clases a partir de las cuales se puedan instanciar objetos que colaboran entre sí para la resolución de problemas. Si asumimos esto, a la vista de las situaciones que ocurren en el mundo real, podremos entender la abstracción.
Cuando estudiamos en el concepto de Herencia en Programación Orientada a Objetos vimos que con ella se podían definir jerarquías de clasificación: los animales y dependiendo de éstos tenemos mamíferos, vertebrados, invertebrados. Dentro de los mamíferos tenemos vacas, perros…
Animal puede ser desde una hormiga a un delfín o un humano. En nuestro cerebro el concepto de animal es algo genérico que abarca a todos los animales: "seres vivos de un "reino" de la existencia". Si defines animal tienes que usar palabras muy genéricas, que abarquen a todos los animales posibles que puedan existir en el mundo. Por ello no puedes decir que animales son aquellos que nacen de huevos, o después de un periodo de gestación en la placenta.
Adonde quiero llegar es que el animal te implica una abstracción de ciertos aspectos. Si lo definimos con palabras no podemos llegar a mucho detalle, porque hay muchos animales distintos con características muy diferentes. Hay características que se quedan en el aire y no se pueden definir por completo cuando pensamos en el concepto de animal "genérico".
Para acabar ¿en el mundo real hay un "animal" como tal? No, ni tan siquiera hay un "mamífero". Lo que tenemos son especímenes de "perro" o "vaca", "hormiga", "cocodrilo", "gorrión" pero no "animal" en plan general. Es cierto que un perro es un animal, pero el concepto final, el ejemplar, es de perro y no animal.
Por tanto "animal", en términos del lenguaje común, podemos decir que es un concepto genérico, pero no una concreción. En términos de POO decimos que es un concepto abstracto, que implementaremos por medio de una clase abstracta. No instanciaremos animales como tal en el mundo, sino que instanciaremos especímenes de un tipo de animal concreto.
En los animales existen propiedades y métodos que pueden ser comunes a todos los animales en general. Los animales podrán tener un nombre o una edad, determinadas dimensiones o podrán desempeñar acciones como morir. Lo que nos debe quedar claro es que no deberíamos poder instanciar un animal como tal. ¿Cómo nace un animal en concreto?, ¿cómo se alimenta? Para responder a esas preguntas necesitamos tener especímenes más concretos. Sí que sé cómo nace o cómo se alimenta una hormiga, o un gorrión, pero no lo puedo saber de un animal genérico, porque puede hacerlo de muchas maneras distintas.
Seguiremos trabajando para explicar estos conceptos, pero de momento entendemos que "animal" es una clase abstracta, pero "hormiga", "perro" o "gorrión" no serían clases abstractas, que sí podríamos instanciar.
Herencia y abstracción
Si entendemos el concepto de herencia podremos entender mejor la abstracción y cómo se implementa.
Recuerda nuestro ejemplo: Tengo animales. Hemos acordado que no puedo tener un animal concreto instanciado en un sistema. Si acaso tendré instancias de perros, saltamontes o lagartijas. Pues bien, en los esquemas de herencia este caso nos puede surgir muy habitualmente.
En la clase "animal" puedo tener determinadas propiedades y acciones implementadas. Por ejemplo, todos los animales pueden tener un nombre, o una edad (ya sean segundos, días o años de edad). También es posible que pueda definir diversas acciones de una vez para todos los animales de una jerarquía de herencia, por ejemplo, la acción de morir, pues todos morimos igual (simplemente dejamos de existir aunque aquí dependiendo de las creencias de cada uno esto pueda ser discutible).
Aunque mi sistema no pueda crear animales como tal, tener definidas esas cuestiones comunes a todos los animales me resulta útil para no tener que programarlas de nuevo en todos los tipos de animales que puedan existir. Simplemente las heredaré en las clases hijas, de modo que estarán presentes sin tener que volver a programar todas esas cosas comunes.
Sin embargo hay cosas de los animales que no podré implementar todavía. Atributos como el número de patas, el alcance de la visión, se implementarán a futuro en los tipos de animales que las necesiten, pero fijémonos en las acciones o métodos. Por ejemplo nacer, alimentarse, etc. No sé cómo va a nacer un animal, pero sé que todos los animales del mundo nacen de algún modo (unos nacen de huevos, otros estaban en la barriga de las hembras y nacen a consecuencia de un parto, etc.)
En estos casos nos puede ser útil definir como métodos abstractos en la clase "animal" esos métodos que van a estar presentes en todos los animales, aunque no seamos capaces de implementarlos todavía.
public abstract function nacer();
Esto quiere decir que todos los animales del mundo heredarán un método abstracto llamado nacer. En las clases concretas que hereden de animal y donde ya sepamos cómo nace tal animal, por ejemplo, la gallina, podemos implementar ese método, para que deje de ser abstracto.
public function nacer(){
//se rompe el huevo y nace el pollito que más adelante será una hermosa gallina
}
Hasta ahora sabemos que hay clases que tienen métodos abstractos, que no somos capaces de implementar todavía y clases en las que se heredan métodos abstractos y en las que seremos capaces de implementarlos.
La utilidad de esto la entenderemos mejor en unos instantes, al tratar el polimorfismo, pero de momento debemos ser capaces de asimilar estas definiciones más formales:
"Una clase abstracta es aquella en la que hay definidos métodos abstractos, sobre la que no podremos instanciar objetos" Además, en un esquema de herencia, "Si heredamos de una clase abstracta métodos abstractos, tampoco se podrán instanciar objetos de las clases hijas y tendrán que definirse como abstractas, a no ser que implementemos todos y cada uno de los métodos que se habían declarado como abstractos en la clase padre".
Polimorfismo y abstracción
Creo que si no se examina de cerca la abstracción bajo el prisma del polimorfismo no se puede entender bien la verdadera utilidad de hacer clases abstractas. Pero antes de seguir, recuerda qué es el Polimorfismo en Programación Orientada a Objetos.
Cuando hablamos de polimorfismo explicamos que es una relajación del sistema de tipos por la cual éramos capaces de aceptar objetos de un tipo y de todas las clases hijas. Por ejemplo, tengo la clase "PoligonoRegular". Sé que los polígonos regulares voy a querer conocer su área, pero para saber su área necesito conocer el número de lados que tiene. Entonces la clase "PoligonoRegular" tendrá un método abstracto "dameArea()". Luego, al definir la clase "cuadrado", o el "pentágono", etc. podremos implementar tal método, con lo que dejará de ser abstracto. Tenemos "Alumnos de una Universidad", los alumnos los vas a querer matricular en las universidades, pero dependiendo del tipo de alumno la matrícula se hace diferente, pues no es lo mismo matricular un alumno becario, o de familia numerosa, que uno normal. Entonces, en la clase "alumno" tendré un método abstracto que sea "matriculate()" que podré definir del todo cuando implemente las clases hijas.
Ahora piensa en esto. Gracias a que fueron definidos los métodos abstractos "dameArea()" y "matriculate()" en las clases padres, tengo clara una cosa: cuando trabajo con elementos de la clase "poligonoRegular", sé que a todos los polígonos regulares que pueda recibir les puedo pedir que me devuelvan su área. También sé que a todos los alumnos les puedo pedir que se matriculen en una universidad.
Ahí está la potencia del polimorfismo, recibir un objeto que pertenece a una jerarquía de clasificación y saber que puedo pedirle determinadas cosas. Quizás en la clase padre no pudieron implementarse esos comportamientos, porque no sabíamos el código necesario para ello, pero al menos se declararon que iban a poder realizarse en el futuro en clases hijas. Eso me permite, en un esquema de polimorfismo, que pueda estar seguro que todos los objetos que reciba puedan responder a acciones determinadas, pues en las clases hijas habrán sido definidas necesariamente (si no se definen deberían declararse las clases como abstractas y en ese caso es imposible que me manden objetos de esa clase).
Es cierto que el concepto se puede quedar un poco en "la abstracción" pero cuando practiques un poco te darás cuenta de la esencia de las clases abstractas y entenderás lo útil y necesario que es declarar métodos abstractos para poder implementar el polimorfismo. De momento es todo en cuanto a este concepto, esperamos que este texto te haya servido de algo. Por supuesto, se agradecen los comentarios.
Miguel Angel Alvarez
Fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Com...