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