Dziedziczenie, rzutowanie i specyfikatory.

Programowanie Unity krok po kroku. W ostatniej już części poradnika poświęconej „suchemu” skryptowaniu opowiemy sobie co nieco o dziedziczeniu, rzutowaniu oraz specyfikatorach dostępu.

Specyfikatory dostępu

Na samym wstępie omówimy specyfikatory dostępu, których przykładem jest tajemnicze słówko public używane do tej pory.

Jak sama nazwa wskazuje specyfikatory dostępu służą do tego, aby sprecyzować kto będzie miał dostęp do danego elementu. Używamy trzech: public, private oraz protected.

Oznaczając metodę tudzież pole jako public dajemy do zrozumienia, że dostęp do niego będziemy mieli z każdego miejsca programu. Oznacza to, że gdy dostęp do zasobu w klasie jest publiczny możemy wywołać tę metodę/pole w dowolnej innej klasie. W przypadku gdy oznaczona zostanie jako private, takiej możliwości nie dostaniemy i dane te zostaną ukryte. Dostęp do nich możliwy będzie tylko i wyłącznie w obrębie tej samej klasy (np. wywołanie metody w innej metodzie tej samej klasy). Protected z kolei zachowuje się podobnie jak private, z tą różnicą, że klasy dziedziczące będą miały swobodny dostęp do zasobu, tak jakby został oznaczony jako public.

Ciekawostka.

Choć nie jest ono specifikatorem dostępu zdecydowałem się umieścić je tutaj, gdyż nie zostało przedstawione wcześniej. Chodzi o słówko static. Do czego służy? Oznacza tyle, że dane pole/metoda jest przypisane nie do obiektu klasy, który należy stworzyć, ale do klasy samej w sobie. Żadna instancja nie jest wtedy wymagana, a jej wywoływanie odbywa się w sposób Klasa.metoda() zamiast obiektKlasy.metoda().

 

Dziedziczenie

Dziedziczenie jest jedną z tych funkcjonalności, którym programowanie obiektowe zawdzięcza swoją popularność i użyteczność. Dziedziczenie samo w sobie polega na założeniu, że pewna klasa może zostać rozszerzona przez dowolną liczbę klas, które będą dziedziczyły z niej funkcjonalności, zachowując możliwość deklarowania swoich pól czy metod, które z kolei odziedziczone mogą zostać przez kolejne podklasy i tak dalej.

Dziedziczenie w języku C# stosuje się przez użycie dwukropka, w ten sposób:

public class KlasaPochodna : KlasaBazowa{

}

Deklarujemy, że klasa o nazwie KlasaPochodna jest potomkiem klasy KlasaBazowa, więc będzie zawierać wszystkie jej funkcjonalności.

 

Programowanie Unity. Dziedziczenie.

 

Zwróćmy uwagę na schemat powyżej, pokazuje on jak mogłaby się układać hierarchia dziedziczenia w programie o zwierzętach. Wyjściową klasą jest klasa Zwierze, która zawiera metodę dajGlos(). Metoda ta będzie również dostępna w obiektach klasy Kot oraz Pies gdyż zostanie odziedziczona. Klasy te posiadają również swoje metody, Kot potrafi drapać meble a Pies gryźć kapcie. Potrafią to również dziedziczące po nich klasy, to znaczy, że Pers czy NorweskiLesny również potrafią drapać meble, a ten drugi dodatkowo gubić sierść. Co ciekawe potrafią również dać głos, gdyż odziedziczyli tą metodę z klasy Zwierze, nie w sposób bezpośredni, ale przez klasę Kot.

 

Jak można zauważyć dziedziczenie metody dajGlos() nie jest do końca tym, czego byśmy oczekiwali. Przecież każde zwierzę w inny sposób wydaje dźwięki, psy szczekają, koty miałczą, krowy muczą a ptaki ćwierkają. Nie mogą więc wywoływać tej samej metody, działanie powinno być inne. Dla porządku w programie oraz łatwości wywoływania (na przykład w pętli) metody te powinny nazywać się tak samo w każdej klasie, mimo, że działanie ich będzie się nieco różnić. Jak więc osiągnąć to bez rezygnowania z dziedziczenia? Wykorzystujemy do tego dwa słówka: virtual oraz override.

Słówko virtual służy to oznaczenia metody, której może (choć nie musi) być zastąpione w jednej lub wielu klasach dziedziczących. W naszym przykładzie zastosowane zostałoby w klasie Zwierze, gdzie zapisalibyśmy metodę dajGlos() w ten sposób:

 

public virtual void dajGlos(){

              Console.WriteLine("Zwierze daje glos");

}

 

W klasach dziedziczących, które chciałyby zmienić jej działanie wykorzystalibyśmy słówko override, np. w klasie Kot:

 

public override void dajGlos(){

              Console.WriteLine("Miaaau!");

}

 

Rzutowanie

Rzutowanie jest kolejną nieodłączną częścią programowania obiektowego. W przypadku obiektów rozróżniamy rzutowanie w górę oraz rzutowanie w dół.

Rzutowanie w górę polega na konwersji obiektu klasy dziedziczącej na obiekt klasy bazowej. Wygląda to mniej więcej tak:

Zwierze kot = new Kot();

albo:

Pies pies1 = new Pies();

Zwierze zwierze1 = (Zwierze) pies1;

W tym przykładzie zrzutowaliśmy obiekty klas Kot i Pies (klasy bardziej szczegółowe) na obiekty klasy Zwierze (bardziej ogólnej). Jest to możliwe dlatego, że wiemy, iż skoro obie te klasy dziedziczą po klasie Zwierze, to na pewno będą posiadały funkcjonalności tej klasy. Stąd też jest to rzutowanie bezpieczne.

No dobra, ale do czego to potrzebne? Załóżmy, że chcemy wywołać metodę dajGlos() dla wszystkich zwierząt, niezależnie od gatunku, albo przekazać jakieś zwierzę jako parametr metody. Wtedy niezależnie od tego czy będzie to kot, pies czy krowa – program przetworzy metodę, gdyż każdy z wyżej wymienionych nie jest tylko kotem, psem lub krową, ale również zwierzęciem.

 

Drugim typem rzutowania jest rzutowanie w dół, czyli mapowanie obiektu ogólnego na bardziej szczegółowy, na przykład konwersja obiektu klasy Zwierze na obiekt klasy Pies. W tym przypadku jednak problemem jest to, że dowolne zwierzę niekoniecznie musi posiadać funkcjonalności klasy Pies, więc rzutowanie w ten sposób powodować może nieoczekiwane błędy.

 

Konwersja typów

Specjalnym przypadkiem rzutowania jest konwersja typów. Powoduje ona wymuszenie zmapowania pewnego typu prostego na inny, umożliwiając tym samym przypisanie wartości, co bez konwersji powodowałoby błąd.

Ten przykład spowodowałby błąd kompilacji:

int liczbaInt = 0;

double liczbaDouble = 2.5;

liczbaInt = liczbaDouble;

Dlaczego? Typ int nie jest w stanie przetworzyć ułamka, czyli nie potrafi przyjąć jako swoją wartość liczby zmiennoprzecinkowej typu double.

 

Ten przykład natomiast nie spowoduje błędu, a jedynie nagięcie wartości do odpowiedniego typu:

int liczbaInt = 0;

double liczbaDouble = 2.5;

liczbaInt = (int)liczbaDouble;

 

W tym przypadku wartość liczbaInt zaokrąglona zostanie do 2, gdyż jak wiemy typ prosty int przechowuje jedynie liczby całkowite.

Przejdź do poradnika Unity3D:

Unity3D – poradnik krok po kroku.

Podziel się wiedzą z innymi

Komentarz do postu