Dziedziczenie

Dziedziczenie to technika ponownego wykorzystania kodu, która umożliwia programistom opracowywanie nowych klas w oparciu o istniejące klasy. Te istniejące klasy są często nazywane klasami bazowymi lub nadklasami , natomiast nowe klasy nazywa się podklasami . Kluczową zaletą dziedziczenia jest możliwość ponownego użycia kodu z klasy bazowej, bez jego przerabiania. Co więcej, dziedziczenie nie wymaga zmian w sposobie interakcji innych klas z klasą bazową. Zamiast modyfikować istniejące klasy, które być może zostały już gruntownie przetestowane i są używane, można wykorzystać mechanizm dziedziczenia i potraktować istniejącą klasę jako zintegrowany moduł, który w razie potrzeby rozszerza się o dodatkowe właściwości i metody. Słowo kluczowe extends umożliwia zadeklarowanie, że dana klasa dziedziczy z innej klasy.

Dziedziczenie umożliwia także wykorzystanie polimorfizmu w programach. Polimorfizm to możliwość użycia jednej nazwy dla metody, która zachowuje się różnie w zależności od tego, do jakiego typu danych zostanie zastosowana. Za prosty przykład może posłużyć klasa o nazwie Shape z dwiema podklasami o nazwach Circle i Square. Klasa Shape definiuje metodę o nazwie area() , która zwraca pole powierzchni kształtu. W wypadku zaimplementowania polimorfizmu możliwe będzie wywoływanie metody area() w obiektach typu Circle i Square, a wynik obliczeń będzie za każdym razem poprawny dla danej figury. Dziedziczenie otwiera drogę do stosowania polimorfizmu, pozwalając na przedefiniowanie ( przesłanianie ) metod odziedziczonych z klasy bazowej w podklasach. W poniższym przykładzie metoda area() została przedefiniowana w klasach Circle i Square:

class Shape 
{ 
    public function area():Number 
    { 
        return NaN; 
    } 
} 
 
class Circle extends Shape 
{ 
    private var radius:Number = 1; 
    override public function area():Number 
    { 
        return (Math.PI * (radius * radius)); 
    } 
} 
 
class Square extends Shape 
{ 
    private var side:Number = 1; 
    override public function area():Number 
    { 
        return (side * side); 
    } 
} 
 
var cir:Circle = new Circle(); 
trace(cir.area()); // output: 3.141592653589793 
var sq:Square = new Square(); 
trace(sq.area()); // output: 1

Ponieważ każda klasa jest definicją typu danych, dziedziczenie tworzy szczególną relację między klasą bazową, a klasą, która ją rozszerza. Podklasa z definicji zawiera wszystkie właściwości swojej klasy bazowej, co oznacza, że w miejsce instancji klasy bazowej zawsze można użyć instancji podklasy. Na przykład, jeśli metoda definiuje parametr typu Shape, to dozwolone jest przekazanie do niej parametru typu Circle, ponieważ klasa Circle rozszerza klasę Shape, co ilustruje następujący kod:

function draw(shapeToDraw:Shape) {} 
 
var myCircle:Circle = new Circle(); 
draw(myCircle);

Właściwości instancji i dziedziczenie

Właściwość instancji, niezależnie od tego, czy została zdefiniowana za pomocą słowa kluczowego function , var , czy const , jest dziedziczona przez wszystkie podklasy, chyba że została w klasie bazowej zadeklarowana z atrybutem private . Na przykład klasa Event w języku ActionScript 3.0 ma szereg podklas dziedziczących właściwości wspólne dla wszystkich obiektów zdarzeń.

W przypadku niektórych typów obiektów klasa Event zawiera wszystkie właściwości potrzebne do zdefiniowania zdarzenia. Zdarzenia tego typu nie wymagają użycia właściwości instancji innych niż zdefiniowane w klasie bazowej Event. Do takich zdarzeń należą: zdarzenie complete wywoływane po pomyślnym załadowaniu danych i zdarzenie connect wywoływane po nawiązaniu połączenia sieciowego.

Poniższy przykład stanowi fragment klasy Event, który zawiera niektóre właściwości i metody dziedziczone przez podklasy. Ponieważ te właściwości są dziedziczone, instancja każdej podklasy ma do nich dostęp.

public class Event 
{ 
    public function get type():String; 
    public function get bubbles():Boolean; 
    ... 
 
    public function stopPropagation():void {} 
    public function stopImmediatePropagation():void {} 
    public function preventDefault():void {} 
    public function isDefaultPrevented():Boolean {} 
    ... 
}

Inne typy zdarzeń wymagają szczególnych właściwości, niedostępnych w klasie Event. Ter zdarzenia zdefiniowane są przy użyciu podklas klasy Event, co umożliwia dodawanie nowych właściwości do zestawu zdefiniowanego w klasie Event. Przykładem takiej podklasy jest klasa MouseEvent, która zawiera dodatkowe właściwości unikatowe dla zdarzeń związanych z ruchem myszy lub kliknięciami myszą, takich jak zdarzenia mouseMove i click . Poniższy przykład przedstawia fragment klasy MouseEvent zawierający definicję właściwości istniejących w podklasie, ale nie w klasie bazowej:

public class MouseEvent extends Event 
{ 
    public static const CLICK:String= "click"; 
    public static const MOUSE_MOVE:String = "mouseMove"; 
    ... 
 
    public function get stageX():Number {} 
    public function get stageY():Number {} 
    ... 
}

Specyfikatory dostępu a dziedziczenie

Właściwość zadeklarowana ze słowem kluczowym public jest widoczna w każdym miejscu kodu. Oznacza to, że słowo kluczowe public , w przeciwieństwie do słów private , protected i internal , nie nakłada żadnych ograniczeń na dziedziczenie właściwości.

Właściwość zadeklarowana ze słowem kluczowym private jest widoczna tylko w klasie, w której została zdefiniowana, a zatem nie jest dziedziczona przez podklasy. Ta reguła różni się od obowiązującej w poprzednich wersjach języka ActionScript, w których słowo kluczowe private miało znaczenie bliższe temu, które w języku ActionScript 3.0 ma słowo protected .

Słowo kluczowe protected oznacza, że właściwość jest widoczna nie tylko w klasie, w której została zdefiniowana, lecz także w jej podklasach. W przeciwieństwie do słowa kluczowego protected w języku programowania Java, słowo kluczowe protected w języku ActionScript 3.0 nie powoduje widoczności właściwości dla wszystkich innych klas w tym samym pakiecie. W języku ActionScript 3.0 tylko podklasy mogą mieć dostęp do właściwości zadeklarowanej ze słowem kluczowym protected . Ponadto właściwość zadeklarowana jako protected jest widoczna dla podklasy niezależnie od tego, czy ta podklasa znajduje się w tym samym pakiecie, co klasa bazowa, czy też w innym pakiecie.

Aby ograniczyć widoczność właściwości do pakietu, w którym jest zdefiniowana, należy użyć słowa kluczowego internal lub w ogóle pominąć specyfikator dostępu. Specyfikator dostępu internal jest specyfikatorem domyślnym, który obowiązuje także w wypadku pominięcia specyfikatora. Właściwość oznaczona słowem internal będzie dziedziczona tylko przez podklasy rezydujące w tym samym pakiecie.

Poniższy przykład pozwala przekonać się, w jaki sposób poszczególne specyfikatory dostępu wpływają na dziedziczenie między pakietami. W przedstawionym kodzie zdefiniowano główna klasę aplikacji o nazwie AccessControl oraz dwie pozostałe klasy o nazwach Base i Extender. Klasa Base znajduje się w pakiecie o nazwie foo, a klasa Extender, będąca podklasą klasy Base, znajduje się w pakiecie o nazwie bar. Klasa AccessControl importuje tylko klasę Extender i tworzy instancję klasy Extender, która próbuje uzyskać dostęp do zmiennej o nazwie str zdefiniowanej w klasie Base. Zmienna str jest zadeklarowana jako public , a zatem kod zostanie prawidłowo skompilowany i wykonany w postaci przedstawionej w poniższym wycinku:

// Base.as in a folder named foo 
package foo 
{ 
    public class Base 
    { 
        public var str:String = "hello"; // change public on this line 
    } 
} 
 
// Extender.as in a folder named bar 
package bar 
{ 
    import foo.Base; 
    public class Extender extends Base 
    { 
        public function getString():String { 
            return str; 
        } 
    } 
} 
 
// main application class in file named AccessControl.as 
package 
{ 
    import flash.display.MovieClip; 
    import bar.Extender; 
    public class AccessControl extends MovieClip 
    { 
        public function AccessControl() 
        { 
            var myExt:Extender = new Extender(); 
            trace(myExt.str);// error if str is not public 
            trace(myExt.getString()); // error if str is private or internal 
        } 
    } 
}

Aby sprawdzić, jak inne specyfikatory dostępu wpływają na kompilację i wykonanie powyższego przykładu, zmień specyfikator dostępu zmiennej str na private , protected lub internal , a wcześniej usuń lub ujmij w komentarz następujący wiersz klasy AccessControl :

trace(myExt.str);// error if str is not public

Niedozwolone przesłanianie zmiennych

Właściwości zadeklarowane za pomocą słów kluczowych var lub const są dziedziczone, ale nie mogą być przesłaniane. Przesłonięcie właściwości oznacza przedefiniowanie jej w podklasie. Jedynym rodzajem właściwości, jaki można przesłaniać, są metody pobierające i ustawiające (właściwości zadeklarowane za pomocą słowa kluczowego function ). Mimo że niedozwolone jest przesłonięcie zmiennej instancji, można uzyskać podobną funkcjonalność, tworząc metodę pobierającą i ustawiającą dla zmiennej instancji i przesłaniając te metody.

Przesłanianie metod

Przesłonięcie metody oznacza przedefiniowanie zachowania metody odziedziczonej. Metody statyczne nie są dziedziczone i nie mogą być przesłaniane. Jednak metody instancji są dziedziczone przez podklasy i mogą być przesłaniane, o ile spełnione są następujące dwa kryteria:

  • Metoda instancji nie jest zadeklarowana w klasie bazowej ze słowem kluczowym final . Słowo kluczowe final w metodzie instancji oznacza, że programista chce zapobiec przesłanianiu tej metody w podklasach.

  • Metoda instancji nie jest zadeklarowana w klasie bazowej ze specyfikatorem dostępu private . Jeśli metoda jest oznaczona w klasie bazowej jako private , można ją w podklasie zadeklarować pod tą samą nazwą nawet bez słowa kluczowego override , ponieważ metoda klasy bazowej nie jest widoczna w podklasie.

    Aby przesłonić metodę instancji spełniającą te kryteria, należy w definicji metody w podklasie użyć słowa kluczowego override , a wersja metody w podklasie musi być zgodna z metodą w nadklasie pod następującymi względami:

  • Metoda przesłaniająca musi mieć ten sam specyfikator dostępu, co metoda klasy bazowej. Metody oznaczone jako internal są uznawane za posiadające ten sam specyfikator dostępu, co klasy zadeklarowane bez specyfikatora.

  • Metoda przesłaniająca musi mieć tę samą liczbę parametrów, co metoda klasy bazowej.

  • Parametry metody przesłaniającej musza mieć te same zadeklarowane typy danych, co parametry metody klasy bazowej.

  • Metoda przesłaniająca musi mieć zadeklarowany ten sam typ wartości zwracanej, co metoda klasy bazowej.

Jednak nazwy parametrów w metodzie przesłaniającej nie muszą być identyczne z nazwami parametrów w klasie bazowej, jeśli tylko ich liczba i typy danych odpowiednich parametrów są zgodne z klasą bazową.

Instrukcja super

Przesłaniając metodę, programista często chce uzupełnić zachowanie przesłanianej metody nadklasy, a nie całkowicie je zmienić. Dlatego potrzebny jest mechanizm umożliwiający metodzie podklasy wywoływanie wersji samej siebie zdefiniowanej w nadklasie. Tym mechanizmem jest instrukcja super , która udostępnia odwołanie do bezpośredniej nadklasy. W poniższym przykładzie zdefiniowano klasę o nazwie Base zawierającą metodę o nazwie thanks() oraz podklasę klasy Base o nazwie Extender, która przesłania metodę thanks() . W metodzie Extender.thanks() używana jest instrukcja super w celu wywołania metody Base.thanks() .

package { 
    import flash.display.MovieClip; 
    public class SuperExample extends MovieClip 
    { 
        public function SuperExample() 
        { 
            var myExt:Extender = new Extender() 
            trace(myExt.thanks()); // output: Mahalo nui loa 
        } 
    } 
} 
 
class Base { 
    public function thanks():String 
    { 
        return "Mahalo"; 
    } 
} 
 
class Extender extends Base 
{ 
    override public function thanks():String 
    { 
        return super.thanks() + " nui loa"; 
    } 
}

Przesłanianie metod pobierających i ustawiających

Wprawdzie nie jest możliwe przesłanianie zmiennych zdefiniowanych w nadklasie, można przesłaniać metody pobierające i ustawiające. Poniższy przykładowy kod przesłania metodę pobierająca o nazwie currentLabel zdefiniowaną w klasie MovieClip w języku ActionScript 3.0:

package 
{ 
    import flash.display.MovieClip; 
    public class OverrideExample extends MovieClip 
    { 
        public function OverrideExample() 
        { 
            trace(currentLabel) 
        } 
        override public function get currentLabel():String 
        { 
            var str:String = "Override: "; 
            str += super.currentLabel; 
            return str; 
        } 
    } 
}

Instrukcja trace() w konstruktorze klasy OverrideExample wyświetla wynik Override: null , co oznacza, że program pomyślnie przesłonił odziedziczoną właściwość currentLabel .

Właściwości statyczne — niedziedziczone

Właściwości statyczne nie są dziedziczone przez podklasy. Oznacza to, że właściwości statyczne nie są dostępne dla instancji podklas. Właściwość statyczne jest dostępna tylko za pośrednictwem obiektu klasy, w którym jest zdefiniowana. Na przykład w poniższym kodzie zdefiniowano klasę bazową o nazwie Base oraz podklasę o nazwie Extender rozszerzająca klasę Base. W klasie Base zdefiniowana jest zmienna statyczna o nazwie test . Fragment kodu przedstawiony poniżej nie zostanie bezbłędnie skompilowany w trybie ścisłym i spowoduje błąd w czasie wykonywania w trybie standardowym.

package { 
    import flash.display.MovieClip; 
    public class StaticExample extends MovieClip 
    { 
        public function StaticExample() 
        { 
            var myExt:Extender = new Extender(); 
            trace(myExt.test);// error 
        } 
    } 
} 
 
class Base { 
    public static var test:String = "static"; 
} 
 
class Extender extends Base { }

Dostęp do zmiennej statycznej test można uzyskać wyłącznie za pośrednictwem obiektu klasy, co ilustruje poniższy kod:

Base.test;

Dozwolone jest natomiast zdefiniowanie właściwości instancji o tej samej nazwie, co nazwa właściwości statycznej. Taka właściwość instancji może być zdefiniowana w tej samej klasie, co właściwość statyczna, lub w podklasie. Na przykład klasa Base z poprzedniego przykładu mogłaby zawierać właściwość instancji o nazwie test . Poniższy kod zostanie skompilowany i wykonany bez błędów, ponieważ właściwość instancji jest dziedziczona przez klasę Extender. Kod dałoby się również skompilować i wykonać, gdyby definicja zmiennej instancji test została przeniesiona, ale nie skopiowana, do klasy Extender.

package 
{ 
    import flash.display.MovieClip; 
    public class StaticExample extends MovieClip 
    { 
        public function StaticExample() 
        { 
            var myExt:Extender = new Extender(); 
            trace(myExt.test);// output: instance 
        } 
    } 
} 
 
class Base 
{ 
    public static var test:String = "static"; 
    public var test:String = "instance"; 
} 
 
class Extender extends Base {}

Właściwości statyczne i łańcuch zasięgów

Mimo że właściwości statyczne nie są dziedziczone, należą do łańcuchu zasięgów klasy, która je definiuje, oraz wszystkich podklas tej klasy. Dlatego mówimy, że właściwości statyczne są w zasięgu zarówno klasy, w której są zdefiniowane, jak i wszystkich jej podklas. Oznacza to, że właściwość statyczna jest bezpośrednio dostępna w treści klasy, w której została zdefiniowana oraz we wszystkich podklasach tej klasy.

Poniższy przykład modyfikuje klasy zdefiniowane w poprzednim przykładzie, aby zademonstrować, że statyczna zmienna test zdefiniowana w klasie Base jest w zasięgu klasy Extender. Innymi słowy, klasa Extender może odwoływać się do zmiennej statycznej test bez konieczności poprzedzania nazwy tej zmiennej nazwą klasy, w której zmienna test została zdefiniowana.

package 
{ 
    import flash.display.MovieClip; 
    public class StaticExample extends MovieClip 
    { 
        public function StaticExample() 
        { 
            var myExt:Extender = new Extender(); 
        } 
    } 
} 
 
class Base { 
    public static var test:String = "static"; 
} 
 
class Extender extends Base 
{ 
    public function Extender() 
    { 
        trace(test); // output: static 
    } 
     
}

W wypadku zadeklarowania właściwości instancji o tej samej nazwie, którą ma już właściwość statyczna w tej samej klasie lub nadklasie, właściwość instancji będzie miała wyższy priorytet w łańcuchu zasięgów. Mówimy, że właściwość instancji jest cieniem właściwości statycznej, co oznacza, że zamiast właściwości statycznej będzie używana wartość właściwości instancji. Poniższy przykładowy kod dowodzi, że w razie zdefiniowania w klasie Extender zmiennej instancji o nazwie test instrukcja trace() wyświetli wartość zmiennej instancji, a nie wartość zmiennej statycznej:

package 
{ 
    import flash.display.MovieClip; 
    public class StaticExample extends MovieClip 
    { 
        public function StaticExample() 
        { 
            var myExt:Extender = new Extender(); 
        } 
    } 
} 
 
class Base 
{ 
    public static var test:String = "static"; 
} 
 
class Extender extends Base 
{ 
    public var test:String = "instance"; 
    public function Extender() 
    { 
        trace(test); // output: instance 
    } 
     
}