La clase Array es una de las pocas clases principales que no son finales, lo que significa que es posible crear una subclase de Array. Esta sección proporciona un ejemplo de cómo se puede crear una subclase de Array y se describen algunos de los problemas que pueden surgir durante el proceso.
Como se mencionó anteriormente, en ActionScript los conjuntos no tienen tipo, pero se puede crear una subclase de Array que acepte elementos de un solo tipo de datos específico. El ejemplo de las secciones siguientes define una subclase de Array denominada TypedArray que limita sus elementos a valores del tipo de datos especificado en el primer parámetro. La clase TypedArray se presenta simplemente como un ejemplo de cómo ampliar la clase Array y puede no ser adecuado para fines de producción por diversas razones. En primer lugar, la verificación de tipos se realiza en tiempo de ejecución, no en tiempo de compilación. En segundo lugar, cuando un método TypedArray encuentra un tipo no coincidente, se omite el tipo no coincidente y no se emite ninguna excepción, aunque los métodos pueden ser fácilmente modificados para emitir excepciones. Además, la clase no puede evitar el uso del operador de acceso a un conjunto para insertar valores de cualquier tipo en el conjunto. Por último, el estilo de programación favorece la simplicidad frente a la optimización del rendimiento.
Nota:
puede utilizar la técnica que se describe aquí para crear un conjunto de tipos. Sin embargo, se recomienda utilizar un objeto Vector. Una instancia de Vector es un conjunto de tipos y ofrece rendimiento y otras mejoras en comparación con la clase Array o cualquiera de sus subclases. La finalidad de esta argumentación es demostrar cómo se crea una subclase de Array.
Declaración de la subclase
La palabra clave
extends
permite indicar que una clase es una subclase de Array. Una subclase de Array debe utilizar el atributo
dynamic
, igual que la clase Array. De lo contrario, la subclase no funcionará correctamente.
El código siguiente muestra la definición de la clase TypedArray, que contiene una constante en la que se almacena el tipo de datos, un método constructor y los cuatro métodos que pueden añadir elementos al conjunto. En este ejemplo se omite el código de cada método, pero se describe y explica completamente en las secciones siguientes:
public dynamic class TypedArray extends Array
{
private const dataType:Class;
public function TypedArray(...args) {}
AS3 override function concat(...args):Array {}
AS3 override function push(...args):uint {}
AS3 override function splice(...args) {}
AS3 override function unshift(...args):uint {}
}
Los cuatro métodos sustituidos utilizan el espacio de nombres AS3 en lugar del atributo
public
, ya que en este ejemplo se supone que la opción de compilador
-as3
está establecida en
true
y la opción de compilador
-es
está establecida en
false
. Esta es la configuración predeterminada para Adobe Flash Builder y AdobeFlashProfessional.
Los programadores expertos que prefieren utilizar herencia de prototipo pueden hacer dos pequeños cambios en la clase TypedArray para que se compile con la opción de compilador
-es
establecida en
true
. En primer lugar, deben quitarse todas las instancias del atributo
override
y debe sustituirse el espacio de nombres AS3 por el atributo
public.
En segundo lugar, debe sustituirse
Array.prototype
para las cuatro instancias de
super
.
Constructor de TypedArray
El constructor de la subclase supone un reto interesante, ya que debe aceptar una lista de argumentos de longitud arbitraria. El reto consiste en pasar los argumentos al superconstructor para crear el conjunto. Si se pasa la lista de argumentos en forma de conjunto, el superconstructor considerará que se trata de un solo argumento de tipo Array y el conjunto resultante siempre tendrá una longitud de 1 elemento. La manera tradicional de controlar las listas de argumentos es utilizar el método
Function.apply()
, que admite un conjunto de argumentos como segundo parámetro, pero la convierte en una lista de argumentos al ejecutar la función. Por desgracia, el método
Function.apply()
no se puede utilizar con constructores.
La única opción que queda es volver a generar la lógica del constructor de Array in el constructor de TypedArray. El código siguiente muestra el algoritmo utilizado en el constructor de clase Array, que se puede reutilizar en el constructor de la subclase de Array:
public dynamic class Array
{
public function Array(...args)
{
var n:uint = args.length
if (n == 1 && (args[0] is Number))
{
var dlen:Number = args[0];
var ulen:uint = dlen;
if (ulen != dlen)
{
throw new RangeError("Array index is not a 32-bit unsigned integer ("+dlen+")");
}
length = ulen;
}
else
{
length = n;
for (var i:int=0; i < n; i++)
{
this[i] = args[i]
}
}
}
}
El constructor de TypedArray comparte la mayor parte del código del constructor de Array, con tan solo cuatro cambios. En primer lugar, la lista de parámetros incluye un nuevo parámetro requerido de tipo Class que permite especificar el tipo de datos del conjunto. En segundo lugar, el tipo de datos pasado al constructor se asigna a la variable
dataType
. En tercer lugar, en la sentencia
else
, el valor de la propiedad
length
se asigna después del bucle
for
, de forma que
length
incluya únicamente argumentos del tipo adecuado. Por último, el cuerpo del bucle
for
utiliza la versión sustituida del método
push()
de forma que solo se añadan al conjunto los argumentos que tengan el tipo de datos correcto. En el siguiente ejemplo se muestra la función constructora de TypedArray:
public dynamic class TypedArray extends Array
{
private var dataType:Class;
public function TypedArray(typeParam:Class, ...args)
{
dataType = typeParam;
var n:uint = args.length
if (n == 1 && (args[0] is Number))
{
var dlen:Number = args[0];
var ulen:uint = dlen
if (ulen != dlen)
{
throw new RangeError("Array index is not a 32-bit unsigned integer ("+dlen+")")
}
length = ulen;
}
else
{
for (var i:int=0; i < n; i++)
{
// type check done in push()
this.push(args[i])
}
length = this.length;
}
}
}
Métodos sustituidos de TypedArray
La clase TypedArray reemplaza los cuatro métodos de la clase Array que pueden añadir elementos a un conjunto. En cada caso, el método sustituido añade una verificación de tipos que evita la adición de elementos que no tienen el tipo de datos correcto. Posteriormente, cada método llama a la versión de sí mismo de la superclase.
El método
push()
repite la lista de argumentos con un bucle
for..in
y realiza una verificación de tipos en cada argumento. Cualquier argumento que no sea del tipo correcto se quitará del conjunto
args
con el método
splice()
. Una vez finalizado el bucle
for..in
, el conjunto
args
solo incluirá valores de tipo
dataType
. A continuación, se llama a la versión de
push()
de la superclase con el conjunto
args
actualizada, como se indica en el código siguiente:
AS3 override function push(...args):uint
{
for (var i:* in args)
{
if (!(args[i] is dataType))
{
args.splice(i,1);
}
}
return (super.push.apply(this, args));
}
El método
concat()
crea un objeto TypedArray temporal denominado
passArgs
para almacenar los argumentos que superen la verificación de tipos. Esto permite reutilizar el código de verificación de tipos que existe en el método
push()
. El bucle
for..in
repite el conjunto
args
y llama a
push()
en cada argumento. Como
passArgs
es de tipo TypedArray, se ejecuta la versión de
push()
de TypedArray. A continuación, el método
concat()
llama a su propia versión de la superclase, como se indica en el código siguiente:
AS3 override function concat(...args):Array
{
var passArgs:TypedArray = new TypedArray(dataType);
for (var i:* in args)
{
// type check done in push()
passArgs.push(args[i]);
}
return (super.concat.apply(this, passArgs));
}
El método
splice()
admite una lista de argumentos arbitraria, pero los dos primeros argumentos siempre hacen referencia a un número de índice y al número de elementos que se desea eliminar. Por esta razón, el método
splice()
sustituido solo hace la verificación de tipos para los elementos del conjunto
args
cuya posición de índice sea 2 o superior. Un aspecto interesante del código es que parece una llamada recursiva a
splice()
desde el bucle
for
, pero no es una llamada recursiva, ya que
args
es de tipo Array, no TypedArray, lo que significa que la llamada a
args.splice()
es una llamada a la versión del método de la superclase. Una vez finalizado el bucle
for..in
, el conjunto
args
solo incluirá valores del tipo correcto en posiciones cuyo índice sea 2 o superior, y
splice()
llamará a su propia versión de la superclase, como se indica en el siguiente código:
AS3 override function splice(...args):*
{
if (args.length > 2)
{
for (var i:int=2; i< args.length; i++)
{
if (!(args[i] is dataType))
{
args.splice(i,1);
}
}
}
return (super.splice.apply(this, args));
}
El método
unshift()
, que añade elementos al principio de un conjunto, también acepta una lista de argumentos arbitraria. El método
unshift()
sustituido utiliza un algoritmo muy similar al utilizado por el método
push()
, como se indica en el siguiente ejemplo código:
AS3 override function unshift(...args):uint
{
for (var i:* in args)
{
if (!(args[i] is dataType))
{
args.splice(i,1);
}
}
return (super.unshift.apply(this, args));
}
}