Rozszerzanie klasy Array

Flash Player 9 i nowsze wersje, Adobe AIR 1.0 i nowsze wersje

Klasa Array jest jedną z niewielu klas głównych, które nie są skończone, co oznacza, że możliwe jest utworzenie dla nich własnych podklas. W tej sekcji zamieszczono przykład tworzenia podklasy klasy Array oraz omówiono niektóre z problemów, jakie mogą pojawić się w tym procesie.

Tak jak wspomniano powyżej, tablice w języku ActionScript nie są typowane; można jednak utworzyć podklasę klasy Array, która będzie przyjmowała elementy tylko o zadanym typie danych. Przykład opisany w poniższych sekcjach ilustruje definiowanie podklasy Array o nazwie TypedArray ograniczającej jej elementy do wartości o typie danych określonym pierwszym parametrem. Klasa TypedArray jest rzadko przedstawiana jako przykład sposoby rozszerzenia klasy Array i może nie być odpowiednia do celów produkcyjnych z kilku względów. Po pierwsze, sprawdzanie typu odbywa się w czasie wykonania, nie zaś podczas kompilacji. Po drugie, gdy metoda TypedArray napotyka niezgodność, niezgodność ta jest ignorowana, a wyjątki nie są odrzucane, mimo że w przypadku metod taka modyfikacja w celu odrzucania wyjątków jest bardzo prosta. Po trzecie, klasa nie może uniemożliwić użycia operatora dostępu do tablicy do wstawienia wartości dowolnego typu do tabeli. Po czwarte, prostota konstrukcji tego kodu została osiągnięta kosztem optymalizacji jego działania.

Uwaga: Za pomocą techniki opisanej w tej sekcji można utworzyć tablicę określonego typu. Jednak lepszym sposobem jest użycie obiektu Vector. Instancja Vector jest tablicą określonego typu i zapewnia wyższą wydajność oraz inne korzyści w porównaniu z klasą Array i jej podklasami. Celem niniejszej dyskusji jest zaprezentowanie sposobu tworzenia podklasy Array.

Deklarowanie podklas

Do wskazania, że dana klasa jest podklasą klasy Array, należy użyć słowa kluczowego extends . Podklasa klasy Array powinna korzystać z atrybutu dynamic , podobnie jak klasa Array. W przeciwnym razie podklasa użytkownika nie będzie działać prawidłowo.

W poniższym kodzie zaprezentowano definicję klasy TypedArray, która zawiera stałą przechowującą typ danych, metodę konstruktora oraz cztery metody umożliwiające dodawanie elementów do tablicy. Kod dla każdej z metod w tym przykładzie pominięto — został on dokładnie zacytowany i wyjaśniony w dalszych sekcjach.

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 {} 
}

Wszystkie cztery nadpisane metody korzystają z przestrzeni nazw AS3 zamiast atrybutu public , ponieważ w tym przykładzie zakłada się, że opcję kompilatora -as3 ustawiono na wartość true , a opcję kompilatora -es — na false . Są to ustawienia domyślne programów Adobe Flash Builder oraz Adobe Flash Professional.

Dla zaawansowanych programistów preferujących korzystanie z prototypów pozostawiono możliwość dokonania dwóch niewielkich zmian w klasie TypedArray w celu wymuszenia jej kompilacji z opcją kompilatora -es ustawioną na wartość true . Należy wówczas najpierw usunąć wszelkie wystąpienia atrybutu override oraz zastąpić przestrzeń nazw AS3 atrybutem public . Następnie należy zastąpić właściwość Array.prototype dla wszystkich czterech wystąpień wartości super .

Konstruktor TypedArray

Podklasa konstruktora wiąże się z pewnym interesującym, acz problematycznym, zagadnieniem; konstruktor bowiem musi przyjmować listę argumentów o arbitralnie ustalonej długości. Pytanie brzmi: w jaki sposób argumenty mają być przekazywane do superkonstruktora w celu utworzenia tablicy. W przypadku przekazania listy argumentów w postaci tablicy superkonstruktor uważa ją za pojedynczy argument typu Array, a wynikowa tablica ma zawsze długość 1 elementu. Tradycyjnym rozwiązaniem tej kwestii jest zastosowanie metody Function.apply() , która pobiera tablicę argumentów jako drugi parametr, lecz konwertuje ją na listę argumentów podczas wykonywania funkcji. Metody Function.apply() nie można jednak zastosować w przypadku konstruktorów.

Jedyną opcją jest ponowne utworzenie logiki konstruktora Array w konstruktorze TypedArray. W poniższym kodzie zaprezentowano algorytm używany w konstruktorze klasy Array, którego można użyć ponownie w konstruktorze podklasy 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]  
            } 
        } 
    } 
}

Konstruktor TypedArray współużytkuje większość kody z konstruktora Array, przy tylko czterech zmianach w kodzie. Po pierwsze lista parametrów obejmuje nowy, wymagany parametr typu class, umożliwiający określenie typu danych tablicy. Następnie typ danych przekazany do konstruktora jest przypisywany do zmiennej dataType . Po trzecie, w instrukcji else wartość właściwości length jest przypisywana po pętli for w taki sposób, że wartość length obejmuje wyłącznie argumenty odpowiedniego typu. Po czwarte, w treści pętli for używana jest zastąpiona wersja metody push() , przez co tylko argumenty o właściwym typie danych są dodawane do tablicy. Poniższy przykład demonstruje działanie konstruktora 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; 
        } 
    } 
}

TypedArray, metody zastąpione przez

Klasa TypedArray zastępuje cztery metody klasy Array odpowiadające za dodawanie elementów do tablicy. W każdym z przypadków metoda zastąpiona dodaje kontrolę typu uniemożliwiającą dodawanie do tablicy elementów o niewłaściwym typie danych. Podobnie każda metoda wywołuje wersję podklasy samej siebie.

Metoda push() iteruje argumenty listy za pomocą pętli for..in , sprawdzając jednocześnie typ każdego z argumentów. Każdy z argumentów, który nie należy do właściwego typu, jest usuwany z tablicy args za pomocą metody splice() . Po zakończeniu pętli for..in tablica args zawiera wartości tylko jednego typu danych: dataType . Następnie wywoływana jest należąca do klasy nadrzędnej wersja metody push() dla zaktualizowanej tablicy args , zgodnie z poniższym kodem przykładowym.

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

Metoda concat() tworzy tymczasowo tablicę TypedArray o nazwie passArgs do przechowywania argumentów, które pomyślnie przeszły etap sprawdzania typów. Umożliwia to ponowne użycie kodu sprawdzania typów istniejącego w metodzie push() . Pętla for..in iteruje tablicę args i wywołuje metodę push() dla każdego z argumentów. Ze względu na to, że zmienna passArgs ma typ TypedArray, wykonywana jest wersja TypedArray metody push() . Następnie metoda concat() wywołuje własną wersję klasy nadrzędnej, zgodnie z poniższym kodem.

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

Metoda splice() pobiera dowolną listę argumentów, lecz dwa pierwsze z nich zawsze odnoszą się do numeru indeksu oraz liczby elementów do usunięcia. Wyjaśnia to, dlaczego zastąpiona metoda splice() sprawdza typy wyłącznie dla elementów tablicy args o indeksie 2 i kolejnych indeksach. Jedną z interesujących kwestii w kodzie jest wrażenie powstania wywołania rekurencyjnego metody splice() wewnątrz pętli for . W rzeczywistości nie jest to wywołanie rekurencyjne, ponieważ tablica args jest typu Array, nie zaś TypedArray, co oznacza, że wywołanie metody args.splice() jest tak naprawdę wywołaniem wersji klasy nadrzędnej tej metody. Po zakończeniu pętli for..in tablica args zawiera wyłącznie wartość prawidłowego typu w położeniach indeksu 2 i wyższych, a metoda splice() wywołuje własną wersję z klasy nadrzędnej, tak jak w poniższym kodzie.

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

Metoda unshift() , dodająca elementy na początek tablicy, przyjmuje również dowolną listę argumentów. Zastępowana metoda unshift() korzysta z algorytmu bardzo podobnego do tego używanego przez metodę push() , tak jak w poniższym przykładowym kodzie.

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