poniedziałek, 15 października 2012

O interfejsach raz jeszcze

O interfejsach napisano już wiele artykułów. Postaram się dlatego podejść do tego zagadnienia od innej strony.
Na początek pytanie: czym różni się metoda wirtualna od implementacji metody z interfejsu?
Mogło by się zdawać, że są to bardzo podobne operacje. Dostarczamy definicję metody zadeklarowanej w innym typie. W interfejsie metoda nie jest wirtualna - przynajmniej nie domyślnie. Klasy dziedziczące po klasie, która implementuje interfejs nie mogą nadpisywać metod zaimplementowanych w klasie bazowej.
Tutaj jest jeden haczyk - można tak zaimplementować metody interfejsu aby istniała możliwość ich nadpisania w klasach potomnych.

Zobaczmy na prosty przykład:

Code:
    public interface IAnimal
    {
        void Voice();
    }


Code:
    public class Animal : IAnimal
    {
        public void Voice()
        {
            Console.WriteLine("Animal");
        }
    }


Code:
    public class Cat : Animal
    {
        public void Voice()
        {
            Console.WriteLine("Cat");
        }
    }

Wywołanie funkcji w mainie:

Code:
        static void Main(string[] args)
        {
            var cat = new Cat();
            cat.Voice();
            var animal = cat as IAnimal;
            animal.Voice();
        }

Efekt który można przewidzieć:

Code:
Cat
Animal

Zdarza się jednak, że chcemy w potomnych klasach przedefiniować funkcję implementowaną z interfejsu w klasie bazowej. Mamy dwie możliwości. Jeżeli nie mamy dostępu do klasy bazowej możemy zaimplementować interfejs w klasie dziedziczącej:

Code:
    public class Cat : Animal, IAnimal
    {
        public new void Voice()
        {
            Console.WriteLine("Cat");
        }
    }


Wynik:

Code:
Cat
Cat

Jednak wersja bazowa jest nadal dostępna:


Code:
            var cat = new Cat();
            cat.Voice();
            var animal = cat as IAnimal;
            animal.Voice();
            var animalBaseClass = cat as Animal;
            animalBaseClass.Voice();


Wynik:

Code:
Cat
Cat
Animal

Jedną z metod naprawienia tego problemu jest zadeklarowanie metody Voice w klasie bazowej jako wirtualnej, a następnie w klasie dziedziczącej nadpisanie tej metody:

Code:
    public class Animal : IAnimal
    {
        public virtual void Voice()
        {
            Console.WriteLine("Animal");
        }
    }

    public class Cat : Animal, IAnimal
    {
        public override void Voice()
        {
            Console.WriteLine("Cat");
        }
    }

Wynik:
Cat
Cat
Cat

Możemy iść dalej i klasę bazową zadeklarować jako abstrakcyjną (tak można zaimplementować interfejs bez implementacji jego metody):

Code:
    public abstract class Animal : IAnimal
    {
        public abstract void Voice();
    }

Klasa dziedzicząca może następnie zapobiec przeładowywaniu metody Voice w kolejnych potomnych klasach:

Code:
    public class Cat : Animal, IAnimal
    {
        public sealed override void Voice()
        {
            Console.WriteLine("Cat");
        }
    }


Ostatni aspekt. Jeżeli dziedziczymy po klasie bazowej, która implementuje interfejs, dziedzicząca także ma zadeklarowaną implementację w sygnaturze to w dziedziczącej nie musimy implementować metody:

Code:
    public class Cat : Animal, IAnimal
    {
        //empty
    }


Interfejsy pozwalają na abstrakcyjną, wirtualną i zamkniętą implementację. Daje to dużą elastyczność w przypadku tworzenia hierarchii klas.

Brak komentarzy:

Prześlij komentarz