Polimorfizm - podstawy

Bumfszszsz!

Posted by Mrozo on April 18, 2015

Polimorfizm czyli wielopostaciowość

Polimorfizm jest najważniejszym i bardzo szerokim konceptem w programowaniu obiektowym.

Podczas pisania programu o wiele wygodniej jest traktować różne dane w podobny sposób. Bez względu na to, czy trzeba wyświetlić napis, czy może liczbę zazwyczaj czytelniej jest jeśli operacja nazywa się Wyświetl zamiast WyświetlNapis i WyświetlLiczbę. Oczywiście napis jest wyświetlany w inny sposób niż liczba, jednak wspólna nazwa tworzy wygodny abstrakcyjny interfejs.

Istota polimorfizmu:

  • system decyduje o szczegółach, nie programista
  • program ma być łatwo rozszerzalny

Jedną z właściwości klas dziedziczonych jest to, że wskaźnik do niej jest kompatybilny typem do wskaźnika wskazującego klasę rodzica:

class Polygon
{
  protected:
    int width, height;
  public:
    void setup (int _width, int _height)
    {
      width = _width;
      height = _height;
    }

    int area()
    {
      cout << "Polygon area" << endl;
      return (0);
    }
};

class Rectangle : public Polygon
{
  public:
    int area()
    {
      cout << "Rectangle area" << endl;
      return (width * height);
    }
};

class Triangle : public Polygon
{
  public:
    int area()
    {
      cout << "Triangle area" << endl;
      return (width * height / 2);
    }
};

int main ()
{
  Rectangle rectangle;
  Triangle triangle;

  Polygon * polygon1 = &rectangle;
  Polygon * polygon2 = &triangle;

  polygon1->setup(2,2);
  polygon2->setup(2,2);

  polygon1->area();
  polygon2->area();

  return 0;
}

Wynik:

Polygon area
Polygon area

Zauważ, że klasy trójkąt i kwadrat mają możliwość korzystania z klasy wielokąt. Dlaczego? No bo one SĄ wielokątami! Najtrudniejszym aspektem do ogarniecia jest fakt, iż klasa dziedziczona (dziecko) JEST jednocześnie klasą główną (rodzicem). Taka abstrakcja ;)
W takim przypadku aby funkcja mogła zostać wywołana używamy strzałki "->" zamiast operatora kropki.

Klasy abstrakcyjne i funkcje wirtualne

Co prawda powyższy kod działa, jednak program tworzy taki oto rezultat:

Polygon class area
Polygon class area

A my przecież nie chcemy Polygona tylko dane figury, których pole chcemy obliczyć.

Powodem złego działania jest to, że funkcja area() została już raz zadeklarowana przez kompilator jako wersja zdefiniowana w klasie bazowej - Polygon.

Rozwiązanie: klasa abstrakcyjna. Klasa abstrakcyjna to klasa, w której przynajmniej jedna z metod jest czysto wirtualna, czyli musimy stworzyć metodę czysto wirtualną dodając słowo virtual i przypisując do metody liczbę 0.

virtual int area() = 0;

Metoda ta nie wykonuje żadnego kodu, no bo po co? Przecież klasy głównej używamy tylko do polimorfizmu. Dlaczego? Bo nie obliczysz pola z figury. Nie wiesz z jakiej. Dlatego z klasy abstrakcyjnej nie da się stworzyć obiektu, ona istnieje tylko po to, aby została przysłonięta.

Po tej małej modyfikacji wynik naszego kodu jest następujący:

Rectangle area
Triangle area

Zauważcie, że metody czysto wirtualne tak na prawdę nie istnieją - znowu abstrakcja.

Rozszerzalność (skalowalność)

Czas na przykład przydatności polimorfizmu:

class Postac
{
public:
   virtual void atakuj() = 0; //czysta funkcja wirtualna
};

class Mag : public Postac
{
public:
   virtual void atakuj()
   {
      cout<<"Atak Maga!"<<endl;
   }
};

class Elf : public Postac
{
public:
   virtual void atakuj()
   {
      cout<<"Atak Elfa!"<<endl;
   }
};

void atak(Postac *x) //ta funkcja jest mega uniwersalna!!!!
{
   x -> atakuj();
}

int main()
{
    Mag mag;
    Elf elf;

    Postac * wskaznik;

    wskaznik = &mag;
    wskaznik -> atakuj(); //sposób 1

    wskaznik = &elf;
    atak(wskaznik); //sposób 2

    return 0;
}

Przysłaniające metody atak() stają się automatycznie metodami wirtualnymi. Możemy dodać przed nie słowo virtual aczkolwiek nie musimy.

Teraz spójrz na funkcję atak. Możesz od teraz stworzyć nawet 200 nowych ras do twojej gry, ustawić na nie wskaźnik polimorficzny i wsadzić je do tej funkcji jako argument. Funkcja będzie działała! Nie będziesz musiał tworzyć nowych do każdej rasy z osobna! To się nazywa rozszerzalność.

Dodatki

Pamiętasz może cechy Linuxa? Linux również jest rozszerzalny - właśnie przez polimorfizm!

Kompletny kod - polimorfizm (click)

Kompletny kod - rozszerzalność (click)