Klasa abstrakcyjna jak nazwa wskazuje jest czymś abstrakcyjnym, to znaczy taką klasą, która nie może posiadać instancji. Coś co jest abstrakcyjne (poza sztuką :p ) nie może być realne.

Nasuwa się więc bardzo istotne pytanie.

A po co to? A komu to potrzebne?

Posłużę się tutaj przykładem z wpisu o interfejsach (tutaj) i przykładem jaki tam był.

public interface IFantasyBook
{
    public int GetPageCount();
    public string GetPrice();
    public string GetBookType();
}

public class HarryPotterSeriesBook : IFantasyBook
{
    public int GetPageCount()
    {
        return 300;
    }
    public string GetPrice()
    {
        return "30 złoty";
    }

    public string GetBookType()
    {
        return "Fantasy Book";
    }
}

public class TheLordOfTheRingsSerisesBook : IFantasyBook
{
    public int GetPageCount()
    {
        return 240;
    }
    public string GetPrice()
    {
        return "50 złoty";
    }
    public string GetBookType()
    {
        return "Fantasy Book";
    }
}

W tym przykładzie widać, że użyliśmy interfejsu oraz 2 klas, w których metoda GetBookType wygląda tak samo.

Zasada w programowaniu: Jeżeli coś jest napisane 2 razy to znaczy, że trzeba to uwspólnić, żeby w przyszłości było tylko jedno miejsce, gdzie coś poprawiamy i nie trzeba było szukać we wszystkich plikach czy coś nie jest magicznie powtórzone i będzie powodowało zmianę logiki działania aplikacji. 

I tutaj wchodzi dziedziczenie klas, gdzie możemy wprowadzić klasę, po której będą dziedziczyć klasy HarryPotterSeriesBook oraz TheLordOfTheRingsSerisesBook. Nazwijmy sobie ją FantasyBook. Nie chcemy, żeby dało się stworzyć instancję klasy  FantasyBook.

Instancję klasy HarryPotterSeriesBook można przedstawić jako np.

Instancję klasy TheLordOfTheRingsSerisesBook można przedstawić jako np.

Instancję klasy FantasyBook nie można przedstawić, ponieważ nie ma książki “Fantasy”. Jest to kategoria książek, ale nie konkretna istniejąca książka , a więc nasza klasa FantasyBook jest ….. Abstrakcyjna !!!

Czas na poprawienie przykładu

public interface IFantasyBook
{
    public int GetPageCount();
    public string GetPrice();
    public string GetBookType();
}

public abstract class FantasyBook : IFantasyBook
{
    public abstract int GetPageCount();
    public abstract string GetPrice();
    public string GetBookType()
    {
        return "Fantasy Book";
    }
}

public class HarryPotterSeriesBook : FantasyBook, IFantasyBook
{
    public override int GetPageCount()
    {
        return 300;
    }

    public override string GetPrice()
    {
        return "30 złoty";
    }
}

public class TheLordOfTheRingsSerisesBook : FantasyBook, IFantasyBook
{
    public override int GetPageCount()
    {
        return 240;
    }

    public override string GetPrice()
    {
        return "50 złoty";
    }
}

Mamy to !!! Kod się nie powtarza i oto chodzi 🙂

Można też zauważyć, że 2 metody są abstrakcyjne GetPageCount, GetPrice w naszej abstrakcyjnej klasie FantasyBook, a metoda GetBookType posiada normalną implementację (ciało metody). Metody też mogą być abstrakcyjne, co oznacza, że nie mają ciała i klasy, które dziedziczą po klasie abstrakcyjnej muszą mieć implementację ( co widać w klasach HarryPotterSeriesBook i TheLordOfTheRingsSerisesBook).

Istnieje też możliwość, żeby jednak nadpisać metodę klasy abstrakcyjnej, jeżeli jest taka potrzeba. W tym wypadku potrzebujemy użyć słowa kluczowego override na metodach, które chcemy nadpisać.

public abstract class FantasyBook : IFantasyBook
{
    public abstract int GetPageCount();
    public abstract string GetPrice();
    public virtual string GetBookType()
    {
        return "Fantasy Book";
    }
}

public class HarryPotterSeriesBook : FantasyBook, IFantasyBook
{
    public override int GetPageCount()
    {
        return 300;
    }

    public override string GetPrice()
    {
        return "30 złoty";
    }

    public override string GetBookType()
    {
        return "Super " + base.GetBookType();//zostanie zwrócone "Super Fantasy Book"
    }
}

public class TheLordOfTheRingsSerisesBook : FantasyBook, IFantasyBook
{
    public override int GetPageCount()
    {
        return 240;
    }

    public override string GetPrice()
    {
        return "50 złoty";
    }
}

Jak widać w klasie HarryPotterSeriesBook nadpisana została metoda GetBookType() przez co nie będzie wykonywana metoda GetBookType z klasy abstrakcyjnej jak w przypadku TheLordOfTheRingsSerisesBook tylko zostanie wykorzystana metoda GetBookType, która została nadpisana (override) z klasy HarryPotterSeriesBook.

Ok, to tyle w tym temacie.

Ostatnia kwestia. Klasy abstrakcyjne tak jak interfejsy to podstawy, które warto opanować, bo przy bardziej skomplikowanych programach ułatwiają zdecydowanie pisanie kodu i ograniczają skutecznie ilość powtórzeń kodu, który nigdy nie jest dobry.