Sobre Clases Abstractas

Escrito el 16/09/2003 por Sixto

Últimamente he leído varias afirmaciones sobre que realizar una clase abstracta en el nuevo FlashMx2004 es equivalente a privatizar su contructor. Para los profanos de OOP, al privatizar el contructor, lo que haces es ocultarle a Flash la manera de instanciar esa clase, por lo que no puede hacerlo. A primera vista del ejemplo se podría concluir que una clase abstracta lo único característico que la define es que no es instanciable.


Me he decidido a hablar sobre esta confusión debida a que Dave Yang ha publicado esa información en swfoo.com, en el que afirma sin tapujos que este código actúa como una clase abstracta:

1:class PretendToBeAbstractClass {
2: // private constructor
3: private function PretendToBeAbstractClass() {}
4:}
5:var o:PretendToBeAbstractClass = new PretendToBeAbstractClass();

Evidentemente en su demostración arguye que el resultado de o es null. Es decir, no es instanciada la clase.

Nada más lejos de la realidad, y sinceramente creo muy perjudicial que este tipo de afirmaciones sean leídas por personas que están aprendiendo OOP de manera general.

En síntesis, y simplificando mucho, se puede decir que una clase abstracta es una clase y una interfaz en un sólo bloque de código, por ello no pueden ser instanciada una clase abstracta, por que no se ha definido la parte de la interfaz necesaria para poder instanciarlo.

El modificador abstract de otros lenguajes definen un clase como abstracta, de manera que el compilador sabe que nadie puede instanciar esa clase directamente, y también sabe que (por ese modificador) todo aquel que herede de esa clase, deberá de implementar los métodos marcados a su vez como abstract, para que el corrector del compilador permita que ese bloque de código sea ejecutado. Por tanto el modificador abstract únicamente le sirve al compilador para controlar que nuestro código esta correctamente escrito según el que ha definido la clase que estamos utilizando. Es una ayuda al programador el que no permita que sea instanciable.

Podriamos añadir que una clase abstracta no tiene por que no ser instanciable necesariamente, ya que con abstracta queremos decir que es una clase genérica de la que se pueden derivar clases mas especializadas sobre el problema que se ha modelado. Aunque siempre es posible que definamos una clase que se pueda especializar, pero que puntualmente podamos usar tal cual en entornos específicos.

Como ejemplo podríamos pensar en desarrollar un juego, en el que tu personaje tiene como propiedad radio de visión. El radio de visión hace que tu personaje vea sombras a partir de una posición relativa de él. Además dentro del juego tenemos enemigos, todos heredan de una clase abstracta animal, la cual se representa como una sombra. En c# sería como sigue:

 1:namespace code4net.game.enemies {
2: abstract class Animal {
3: public void walk (int direction) {
4: // Walk
5: }
6: public void run (int direction) {
7: // Run
8: }
9: public abstract void doSound (int time) {}
10: }
11: public class Dog:Animal {
12: public void doSound (int time) {
13: this.bark (time);
14: }
15: public void bark (int time) {
16: // Bark
17: }
18: }
19: public class RealAnimal:Animal {
20: public void doSound (int time) {
21: // Nothing
22: }
23: }
24:}

Como podemos comprobar, en este caso, nos creamos una clase abstracta para que el compilador nos vigile al heredar de animal y nos recuerde que debemos definir de nuevo doSound, pero a veces necesitamos instanciar la clase animal, en este caso es por que cuando un animal esta fuera del radio de visión del jugador nosotros solo debemos representar una sombra y es más rápido instanciar la clase base que instanciar la clase del animal que corresponde y sombrearla gráficamente en el juego, por ello heredamos animal en realAnimal , cuando el jugador se acerque al animal y pueda distinguirle ya le sustituiremos por la clase correcta. Otra forma de hacer lo mismo sería:

   1:namespace code4net.game.enemies {
2: public class Animal {
3: public void walk (int direction) {
4: // Walk
5: }
6: public void run (int direction) {
7: // Run
8: }
9: public virtual void doSound (int time) {}
10: }
11: public class Dog:Animal {
12: public override void doSound (int time) {
13: this.bark (time);
14: }
15: public void bark (int time) {
16: // Bark
17: }
18: }
19:}

Ambas soluciones son equivalentes, lo único que las diferencia es que en la primera usamos el compilador para que nos vigile y en la segunda confiamos en la capacidad del programador. Ambas clases son abstractas, por que definen un elemento abstracto general que puede ser especializado. En AS2 el segundo ejemplo es fácil de implementar:

   1:class Animal {
2: public function walk (direction:Number): Void {}
3: public function run (direction:Number): Void {}
4:}
5:
6:class Dog extends Animal{
7: public function doSound (time:Number):Void {
8: this.bark(time);
9: }
10: public function bark (time:Number):Void {
11: // Bark
12: }
13:}

Evidentemente el primer ejemplo que hemos mostrado antes no se puede implementar, por que no ha sido incluido el modificador para el compilador dentro Flash MX 2004. Aunque una simulación puede ser definir una interfaz con el mismo nombre que la clase abstracta donde introducimos los métodos abstractos, y acordándonos de implementarla siempre que heredamos de la clase, puede que controlemos que definimos bien todos los métodos que han de redefinirse, evidentemente en este ejemplo con un sólo método no es de mucha ayuda, pero en clases con un gran número de métodos abstractos puede sernos útil. El compilador así nos hará el trabajo sucio de comprobar que redefinimos todos los métodos que han de ser redefinidos:

   1:interface IAnimalAbstract {
2: function doSound () : Void;
3:}
4:class AnimalAbstract {
5: public function walk (direction:Number): Void {}
6: public function run (direction:Number): Void {}
7:}
8:
9:class Dog extends AnimalAbstract implements IAnimalAbstract{
10: public function doSound (time:Number):Void {
11: this.bark(time);
12: }
13: public function bark (time:Number):Void {
14: // Bark
15: }
16:}
17:
18:class Animal extends AnimalAbstract implements IAnimalAbstract{
19: public function doSound (time:Number) : Void {
20: // Nothing
21: }
22:}

Pero una clase abstracta también se puede reproducir en AS:


 1:// Animal
2:Animal = function () {}
3:Animal.prototype.walk = function (direction) {
4: // Walk
5:}
6:Animal.prototype.run = function (direction) {
7: // Run
8:}
9:Animal.prototype.stop() {}
10:// Dog
11:Dog = function () {}
12:Dog.prototype = new Animal();
13:Dog.prototype.doSound = function () {
14: this.bark ();
15:}
16:Dog.prototype.bark = function () {
17: // Bark
18:}

Así que es bueno que separemos conceptos de programación de la codificación específica en un lenguaje.