La herencia de clases es uno de los conceptos básicos de la programación orientada a objetos. Decir que una clase hereda de otra quiere decir que esa clase obtiene los mismos métodos y propiedades de la otra clase. Permitiendo de esta forma añadir a las características heredadas las suyas propias.
Supongamos que tenemos una clase "Persona" con los métodos y propiedades básicas de una objeto persona como podrian ser "caminar" o "hablar", podríamos tener otras clases como "Guillermo" o "Elder" que comparten todas las características de una "Persona" pero que añaden características propias. Por lo que "Guillermo" y "Elder" pueden realizar las mismas funciones que puede realizar una "Persona" y además cada una puede realizar las suyas propias, por ejemplo, "Guillermo" sabe nadar pero "Elder" no, y "Elder" sabe bailar reggeton pero "Guillermo" no.
En terminos de programación estaríamos diciendo que "Guillermo" y "Elder" son dos clases especializadas que heredan o extienden de la superclase "Persona".
Tipos de herencia de clases
Existen dos tipos de herencia:
- Herencia por especialización
- Herencia por generalización
En realidad la herencia es la misma, esta es una diferenciación puramente conceptual sobre la forma en que se a llegado a ella.
Una herencia por especialización es la que se realiza cuando necesitamos crear una clase nueva que disponga de las mismas características que otra pero que le añada funcionalidades. Por ejemplo si tenemos una clase que genera un botón simple, y necesitamos crear un botón que sea igual que el anterior pero que además añada un efecto al ser clicado.
La herencia por generalización es la que realizamos cuando tenemos muchas clases que comparten unas mismas funcionalidades y por homogeneizar las partes comunes se decide crear una clase que implemente toda esa parte común y se dejan solo las partes especificas en cada clase. Por ejemplo si tenemos clases para dibujar formas geométricas todas ellas
disponen de las mismas propiedades (un color de fondo, color de linea, etc..), todas estas características pueden estar en una clase general de la que hereden todas las clases concretas, evitando tener que escribir todo ese código común en todas ellas.
Herencia de clases en Actionscript
En Actionscript definimos que una clase hereda de otra con la sentencia "extends".
public class Guillermo extends Persona
Public, private o protected
Una consideración a tener en cuenta de la herencia es que una clase no hereda la propiedades o métodos privados, con lo que no tendrán acceso a ellas. Si necesitamos heredar propiedades o métodos que no queremos que sean accesibles desde fuera de las clases las definiremos como protected.
Sobreescritura de métodos
Una característica muy importante que permite la herencia es que podemos hacer que una clase implemente de manera diferente un método heredado. Haciendo que dos clases que heredan de la misma clase y heredan los mismos métodos se comporten de maneras diferentes.
Por ejemplo, unas clases de dibujo de figuras geométricas pueden heredar de una clase general la función "dibujar". Todas las clases dispondrán de esa función, pero cada clase la implementará de diferente manera y por lo tanto dibujará una figura diferente.
Para sobrescribir un método de la superclase utilizaremos la sentencia override en la definición del método.
//función de la superclase public function traza():void { trace("superclase"); }
//función de la subclase override public function traza():void { trace("subclase"); }
De esta manera al ejecutar la función "traza()" desde una subclase obtendremos el texto "subclase" sobrescribiendo las acciones del método de la subclase (no obtendríamos el texto "superclase").
En el caso de no querer sobrescribir por completo toda la implementación del método de la superclase, si no que lo que queremos es ampliarlo, podemos acceder a la implementación de la superclase con el operador "super".
//función de la subclase override public function traza():void { super.traza(); trace("subclase"); }
En este caso, se ejecutaría la implementación del método en la superclase (obtendriamos el texto "superclase") y luego el de la subclase (obtendríamos también el texto "subclase").
Esto también lo podemos realizar en el constructor de la clase. Es bastante probable que una clase esté definiendo valores dentro de su constructor, de manera que al ser extendida nos interese que esas definiciones se continúen realizando. En este caso deberemos realizar una llamada al constructor de la superclase a través del operador "super".
package { public class A { protected var variable:uint; public function A(n:uint) { variable = n; } } }
package { import A; public class B extends A { public function B(n:uint) { super(n); //ejecuta el contructor de la clase A, enviandole el parámetro } } }
En el caso de que queramos asegurarnos de que una propiedad o método no pueda ser sobrescrita por otra clase la definiremos como final.
//función de la subclase que no permitirá ser sobreescrita public final function traza():void { trace("subclase"); }
Ejemplo del uso de herencia de clases:
Veamos un sencillo ejemplo práctico: Crearemos un par de clases muy simples para dibujar un cuadrado o un círculo.
Lógicamente el primer paso es pensar que características compartirán tanto los cuadrados y los círculos.
- Ambos son elementos gráficos que tienen propiedades que definen su tamaño (size), si disponen de relleno o solo de línea (fill), el color de relleno (bgColor), el grosor de linea (borderSize) y el color de la linea (borderColor).
- Tambien ambás dispondrán de una función que permitirá actualizar el gráfico en caso de modificar sus propiedades.
- Por último amb´s dispondran de la método que dibujará el gráfico. Lógicamente esta será la función que cada clase implementará de forma diferente.
Así que lo que haremos es crear una superclase "Grafico" que disponga de todas las propiedades mencionadas anteriormente, y dejaremos la implementación del método "dibuja()" a las subclases "Cuadrado" y "Circulo".
Empecemos escribiendo la interface de métodos públicos que deberán disponer todos los objetos que extiendan de "Grafico". En este caso serán todos los setters y getters para las propiedades, y el método para actualizar el gráfico.
package org.cristalab.graphics { public interface IGrafico { function get size():uint function set size(n:uint):void function get fill():Boolean function set fill(n:Boolean):void function get bgColor():uint function set bgColor(n:uint):void function get borderSize():uint function set borderSize(n:uint):void function get borderColor():uint function set borderColor(n:uint):void function update():void } }
Escribiremos la superclase "Grafico" que implementará la interface "IGrafico" y definirá todos los setters y getters de las propiedades.
Las propiedades las definiremos como "portected" para que sean privadas pero heredables.
El constructor de la clase recibirá todos los parámetros necesarios para definir las propiedades (les asignaremos valores por defecto por si se crea una instancia sin pasarle parámetros). Y realizará una llamada a la función "dibuja()" que es la que creará el gráfico.
package org.cristalab.graphics { import flash.display.Shape; import flash.display.Sprite; import org.cristalab.graphics.IGrafico; //------------------------------------------------------------------------ public class Grafico extends Sprite implements IGrafico { protected var _size:uint; protected var _fill:Boolean; protected var _bgColor:uint; protected var _borderSize:uint; protected var _borderColor:uint; //------------------------------------------------------------------------ public function Grafico(size:uint = 10, fill:Boolean = true, bgColor:uint = 0x000000, borderSize:uint = 0, borderColor:uint = 0x000000) { _size = size; _fill = fill; _bgColor = bgColor; _borderSize = borderSize; _borderColor = borderColor; dibuja(); } //------------------------------------------------------------------------ public function get size():uint { return _size; } public function set size(n:uint):void { _size = n; } //------------------------------------------------------------------------ public function get fill():Boolean { return _fill; } public function set fill(n:Boolean):void { _fill = n; } //------------------------------------------------------------------------ public function get bgColor():uint { return _size; } public function set bgColor(n:uint):void { _bgColor = n; } //------------------------------------------------------------------------ public function get borderSize():uint { return _borderSize; } public function set borderSize(n:uint):void { _borderSize = n; } //------------------------------------------------------------------------ public function get borderColor():uint { return _borderColor; } public function set borderColor(n:uint):void { _borderColor = n; } //------------------------------------------------------------------------ public function update():void { this.graphics.clear(); dibuja(); } //------------------------------------------------------------------------ protected function dibuja():void { Throw.newError("clase abstracta"); } //------------------------------------------------------------------------ } }
Como hemos dicho, la clase "Grafico" no implementa el método "dibuja()" ya que eso depende de cada subclase, con lo que si intentamos crear una instancia de "Grafico" nos lanzará un error. Lo único que hacemos es definir que todas las subclases hereden el método y cada una lo implenete a su manera.
Escribamos ahora la clase "Cuadrado".
Esta clase extendrá de la clase "Grafico" e implementará la interface "IGrafico". Por lo tanto heredará todas las propiedades y métodos de la superclase marcados como public o protected, con lo que no necesitaremos definir las protiedades ni los setter y getters, pero dispondrá de ellos.
El constructor de esta clase recibirá como parámetros los valores del objeto a crear, y deberemos pasarselos al constructor de la superclase para que asigne los valores y ejecute la función de dibujar. Como hemos mencionado esto lo haremos utilizando la sentencia super();
Ahora solo nos quedará sobreescribir el método "dibuja()" para que se dibuje lo que nos interese en función de la clase actual, en este caso queremo sdibujar un cuadrado. Utilizaremos un override para sobrescribir el método.
package org.cristalab.graphics { import org.cristalab.graphics.IGrafico; import org.cristalab.graphics.Grafico; import flash.display.Shape; import flash.display.Sprite; //------------------------------------------------------------------------ public class Cuadrado extends Grafico implements IGrafico { //------------------------------------------------------------------------ public function Cuadrado(size:uint = 10, fill:Boolean = true, bgColor:uint = 0x000000, borderSize:uint = 0, borderColor:uint = 0x000000) { super(size, fill, bgColor, borderSize, borderColor); } //------------------------------------------------------------------------ override protected function dibuja():void { if (_fill == true) { this.graphics.beginFill(_bgColor); } if (_borderSize != 0) { this.graphics.lineStyle(_borderSize, _borderColor); } this.graphics.drawRect(0, 0, _size, _size); if (_fill == true) { this.graphics.endFill(); } } //------------------------------------------------------------------------ } }
Haremos lo mismo para la clase "Circulo". Únicamente deberemos implementar de diferente manera el método "dibuja".
package org.cristalab.graphics { import org.cristalab.graphics.IGrafico; import org.cristalab.graphics.Grafico; import flash.display.Shape; import flash.display.Sprite; //------------------------------------------------------------------------ public class Circulo extends Grafico implements IGrafico { //------------------------------------------ public function Circulo(size:uint = 10, fill:Boolean = true, bgColor:uint = 0x000000, borderSize:uint = 0, bordeColor:uint = 0x000000) { super(size, fill, bgColor, borderSize, borderColor); } //------------------------------------------------------------------------ override protected function dibuja():void { if (_fill == true) { this.graphics.beginFill(_bgColor); } if (_borderSize != 0) { this.graphics.lineStyle(_borderSize, _borderColor); } var radio:uint = Math.round(_size / 2); this.graphics.drawCircle(radio, radio, radio); if (_fill == true) { this.graphics.endFill(); } } //------------------------------------------------------------------------ } }
A partir de aquí iremos creando tantas clases como gráficos diferentes nos interese dibujar, incluso podemos extender estas subclases en otras subclases, por ejemplo para crear objetos más complejos pero que su base sea un cuadrado o un circulo.
¿Sabes SQL? ¿No-SQL? Aprende MySQL, PostgreSQL, MongoDB, Redis y más con el Curso Profesional de Bases de Datos que empieza el martes, en vivo.
Publica tu comentario
El autor de este artículo ha cerrado los comentarios. Si tienes preguntas o comentarios, puedes hacerlos en el foro
Entra al foro y participa en la discusión
o puedes...
¿Estás registrado en Cristalab y quieres
publicar tu URL y avatar?
Inicia sesión
¿No estás registrado aún pero quieres hacerlo antes de publicar tu comentario?
Registrate