Ampliación de la clase Array

Flash Player 9 y posterior, Adobe AIR 1.0 y posterior

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)); 
    } 
}