Metody i dzieCzęść NR.12Wraz z dziedziczeniem klas warto byłoby opisać wszelkie zachowania i mechanizmy metod, które ułatwiają życie programiście.

 

 

Deklarowanie przysłoniętej metody

Pewien problem wiąże się z deklarowaniem właściwości pól, metod w klasie, która będzie uczestniczyła w dużej hierarchii dziedziczenia. Otóż w którymś przypadku może będziesz chciał użyć ponownie pewnej nazwy metody, która jest już zajęta przez klasę bazową w hierarchii. W wyniku deklaracji metody już istniejącej w hierarchii kompilator zgłosi ostrzeżenie w czasie kompilacji.

Ostrzeżenie_thumb[4]

Metoda w klasie dziedziczonej przysłania metodę w klasie bazowej.


Oto kod, który wywoła ten przypadek:

class Ssak
{
    public void SenRem()
    { }
}

class Kot : Ssak
{
    public void SenRem()
    { }

    public void Miauczenie()
    {}    
}

Mimo iż kod się skompiluje, ponieważ to, co otrzymywany to ciągle ostrzeżenie , a nie błąd, to jednak jest to poważna sprawa. Jeśli użyjemy metody “SenRem” w obiekcie Kot ktoś może się spodziewać , że zostanie użyta metoda w klasie Ssak, ponieważ np. tak się dzieje u wszystkich innych klas dziedziczących po ssakach.

Jednak ponieważ metoda jest przysłaniana dla obiektu Kot, to zajdzie jego implementacja metody , a nie klasy bazowej.

Takie zachowanie nie jest zachwalane, ponieważ łamie jedną z zasad S.O.L.I.D. Po obiektach dziedziczących spodziewamy się takiego samego zachowania jak dla klas bazowych

Jednak jeśli jesteś przekonany , że to co robisz jest właściwym rozwiązaniem “X” istnieje sposób na usunięcie tego ostrzeżenia. W ten sposób również zaznaczysz, że metoda jest implementacją już istniejącej metody w klasie bazowej.

Wystarczy , że napiszemy słowo kluczowe new dla metody przesłaniającej.

class Ssak
{
    public void SenRem()
    { }
}

class Kot : Ssak
{
    new public void SenRem()
    { }

    public void Miauczenie()
    {}    
}

Słowo kluczowe new niczego nie zmienia jest to sygnał dla kompilatora , że “wiemy co robimy i wiemy czym to grozi”. Jest to też sygnał dla innego programisty , że podobna metoda już istnieje w hierarchii dziedziczenia.

Wirtualne metody – słowo kluczowe Virtual

Co to jest metoda wirtualna.

Najlepszym przykładem metody wirtualnej jest metoda “ToString()” w klasie System,Object. Celem tej metody jest przekonwertowanie danych obiektu na jego reprezentację tekstową. Ponieważ metoda ta znajduje się w klasie System.Object , a wszystkie klasy w .NET dziedziczą po niej, to naturalnie wszystkie klasy nawet te przez nas utworzone posiadają tą metodę.

Metoda ta jednak nie działa tak magicznie. Różne klasy mają różne pola i przechowują różne wartości. Metoda ToString() domyślnie wyświetla przestrzeń nazw, w której znajduje się dana klasa i nazwę klasy.

Czyli dla obiektu Kot metoda ToString() powinna wyświetlić “ConsoleApplication2.Kot”.

ToString dla obiektu Kot

Nic więc dziwnego , że metoda ToString() jest metodą wirtualną i może być przez nas nadpisana. Pamiętaj, że nadpisanie metody, a jej przysłonięcie to dwie różne rzeczy. Metoda, która celowo ma być nadpisana jest metodą wirtualną, to po pierwsze. Po drugie, nadpisywanie metody jest to mechanizm, który oferuje różne implementacje dla tej samej metody. Cel metody jest zawsze taki sam tylko dostosowany do danego obiektu.

Przysłaniając metodę tak naprawdę całkowicie ją zastępujemy i może ona w ogóle wykonywać inne polecenie.

Nadpisywanie metod jest często spotykanym mechanizmem w programowaniu. Przysłaniania metody natomiast , jest dużym sygnałem błędnego modelowania rzeczywistości na klasy.

Dla programistów Java
W Javie domyślnie wszystkie metody są wirtualne , ale w C# tak nie jest.

Aby stworzyć metodę wirtualną musisz użyć słowa kluczowego virtual . Oto moja implementacja kodu:

class Ssak
{
    public virtual void SenRem()
    { }
}

A tak wygląda kod metody ToString() dla klasy System.Object.

namespace System
{
    public class Object 
    {
        public virtual string ToString()
        {
            //Coś 
        }       
    }
}

 

Wirtualne metody – słowo kluczowe Override

Zadeklarowanie metody wirtualnej jeszcze nie dało nam możliwości użycia tej metody na swój sposób. Bez zastosowania kolejnego słowa kluczowego kompilator domyślnie uzna , że przysłaniamy metodę.

Brak użycia słowa override

Aby nadpisać metodę musimy użyć słowa kluczowego overrride.

class Ssak
{
    public virtual void SenRem()
    { }

    public override string ToString()
    { 
        return "Jestem Ssakiem"; 
    }
}

Teraz metoda ToString() wyświetli coś innego niż przestrzeń nazw danej klasy.

Konsola i test ToString()

Aby wszystkie klasy dziedziczące po Ssakach (czyli Kot i Pies) wyświetlały tę wartość można w metodzie nadpisanej odwołać się do klasy bazowej za pomocą słowa kluczowego base().

class Kot : Ssak
{
    public override string ToString()
    {
        return base.ToString();
    }

    public void Miauczenie()
    {}    
}

Nie jest to konieczne, ponieważ domyślnie Kot by wyświetlił wynik działania metody ToString() z klasy bazowej. Ale kod jest bardziej czytelny.

Zauważyłem pewną ważną rzecz, jeśli chodzi o przypisywanie klas do swoich klas bazowych z zastosowaniem metod nadpisanych.

Wirtualne metody i polimorfizm

Jak dokładnie działają metody przysłaniające, kiedy zachodzi polimorfizm. Oto przykład:

class Ssak{
    public override string ToString()
    { 
        return "Jestem Ssakiem"; 
    }
}

class Kot : Ssak{
    public override string ToString()
    {
        return "Jestem Kotem";
    }
}

class Pies : Ssak{
    public override string ToString()
    {
        return "Jestem Psem";
    }
}

class Krowa : Ssak{
}

Powyższy kod pokazuje klasę Ssak, która implementuje swoją wersje metody ToString() jak Pies i Kot. Krowa nie posiada swojej wersji tej metody.

Jakie dane zostaną zapisane w tablicy string, jeśli zajdzie polimorfizm, czyli zmienię Psa , Kota i Krowę w Ssaka.

Ssak ssak;
Kot kot = new Kot();
Pies pies = new Pies();
Krowa krowa = new Krowa();

string[] sprawdze = new string[3];

ssak = kot;
sprawdze[0] = ssak.ToString();
ssak = pies;
sprawdze[1] = ssak.ToString();
ssak = krowa;
sprawdze[2] = ssak.ToString();

Oto rezultat kodu:

Polimorfizm i override

Co tu się stało.

Za każdym razem kod wywołuje metodę “ToString” dla zmiennej ssak. Jak widać tylko ostatni zapis miał łańcuch “Jestem Ssakiem” ponieważ krowa nie ma swojej wersji tej metody.

Dla psa i kota pomimo zapisu do klasy Ssak metoda ToString() wyświetliła swoje własne unikalne wartości. Ponieważ metoda jest wirtualna program rozumie , że musi wywołać metodę “ToString()” dla Psa i Kota.

To zjawisko nazywa się polimorfizmem. Zachodzi ono wtedy, kiedy ta sama metoda w danym obiekcie może mieć różne implementacje w zależności od jej zawartości. Polimorfizm to nic innego jak fakt , że dany obiekt może mieć wiele form.

Zasady użycia metod wirtualnych i metod nadpisanych

Istnieje pewna grupa zasad, które muszą być przestrzegane , aby mechanizm metod wirtualnych działał poprawnie.

  • Metody wirtualne i metody nadpisujące nie mogą być prywatne.
  • Nazwy metody wirtualnej i nadpisującej muszą być takie same. Wielkość znaków się liczy.
  • Metoda wirtualna i metoda nadpisująca muszą mieć ten sam poziom dostępności. Czyli muszą mieć taki sam modyfikator dostępu.
  • Możesz nadpisać tylko metodę wirtualną.
  • Tak jak napisałem wcześniej, jeśli metoda nadpisująca nie ma słowa kluczowego override kompilator uznaje ,że chcesz przysłonić metodę, czyli stworzyć kompletnie inną implementacje.
  • Nie możesz zadeklarować równocześnie , że dana metoda jest wirtualna i równocześnie nadpisująca.
  • Jeśli chodzi o mechanizm dziedziczenia każda klasa dziedzicząca po klasie, która nadpisała metodę X może nadpisać ją jeszcze raz na swój sposób.

Co dalej:

W następnych wpisach omówię interfejsy a później klasy abstrakcyjne.