Zjawisko polimorfizmu pozwala na znaczne uproszczenie większości algorytmów, w których dużą rolę odgrywa zarządzanie wieloma różnymi obiektami. Nie chodzi tu wcale o jakieś skomplikowane operacje sortowania, wyszukiwania, kompresji itp., tylko o często spotykane operacje wykonywania tej samej czynności dla wielu obiektów różnych rodzajów.
Opis ten jest w założeniu dość ogólny, bowiem sposób, w jaki używa się obiektowych technik polimorfizmu jest ściśle związany z konkretnymi programami.
Prosty przypadek wykorzystania polimorfizmu opiera się na elementarnej i rozsądnej zasadzie. Mianowicie:
Wskaźnik na obiekt klasy bazowej może wskazywać także na obiekt którejkolwiek z jego klas pochodnych.
Bezpośrednie przełożenie tej reguły na konkretne zastosowanie programistyczne jest dość proste. Przypuśćmy więc, że mamy taką oto hierarchię klas:
#include <string>
#include <ctime>
// klasa dowolnego dokumentu
class CDocument
{
protected:
// podstawowe dane dokumentu
std::string m_strAutor; // autor dokumentu
std::string m_strTytul; // tytuł dokumentu
tm m_Data; // data stworzenia
public:
// konstruktory
CDocument()
{ m_strAutor = m_strTytul = "???";
time_t Czas = time(NULL); m_Data = *localtime(&Czas); }
CDocument(std::string strTytul)
{ CDocument(); m_strTytul = strTytul; }
CDocument(std::string strAutor, std::string strTytul)
{ CDocument();
m_strAutor = strAutor;
m_strTytul = strTytul; }
//-------------------------------------------------------------
// metody dostępowe do pól
std::string Autor() const { return m_strAutor; }
std::string Tytul() const { return m_strTytul; }
tm Data() const { return m_Data; }
};
//----------------------------------------------------------------------
// dokument internetowy
class COnlineDocument : public CDocument
{
protected:
std::string m_strURL; // adres internetowy dokumentu
public:
// konstruktory
COnlineDocument(std::string strAutor, std::string strTytul)
{ m_strAutor = strAutor; m_strTytul = strTytul; }
COnlineDocument (std::string strAutor,
std::string strTytul,
std::string strURL)
{ m_strAutor = strAutor;
m_strTytul = strTytul;
m_strURL = strURL; }
//-------------------------------------------------------------
// metody dostępowe do pól
std::string URL() const { return m_strURL; }
};
// książka
class CBook : public CDocument
{
protected:
std::string m_strISBN; // numer ISBN książki
public:
// konstruktory
CBook(std::string strAutor, std::string strTytul)
{ m_strAutor = strAutor; m_strTytul = strTytul; }
CBook (std::string strAutor,
std::string strTytul,
std::string strISBN)
{ m_strAutor = strAutor;
m_strTytul = strTytul;
m_strISBN = strISBN; }
//-------------------------------------------------------------
// metody dostępowe do pól
std::string ISBN() const { return m_strISBN; }
};
Z klasy
CDocument, reprezentującej dowolny dokument, dziedziczą dwie następne:
COnlineDocument, odpowiadająca tekstom dostępnym przez Internet, oraz
CBook, opisująca książki.
Napiszmy również odpowiednią funkcję, wyświetlającą podstawowe informacje o podanym dokumencie:
#include <iostream>
void PokazDaneDokumentu(CDocument* pDokument)
{
// wyświetlenie autora
std::cout << "AUTOR: ";
std::cout << pDokument->Autor() << std::endl;
// pokazanie tytułu dokumentu
std::cout << "TYTUL: ";
std::cout << "\"" << pDokument->Tytul() << "\"" << std::endl;
// data utworzenia dokumentu
// (pDokument->Data() zwraca strukturę typu tm, do której pól
// można dostać się tak samo, jak do wszystkich innych - za
// pomocą operatora wyłuskania . (kropki))
std::cout << "DATA : ";
std::cout << pDokument->Data().tm_mday << "."
<< (pDokument->Data().tm_mon + 1) << "."
<< (pDokument->Data().tm_year + 1900) << std::endl;
}
Bierze ona jeden parametr, będący zasadniczo wskaźnikiem na obiekt typu
CDocument. W jego charakterze może jednak występować także wskazanie na któryś z obiektów potomnych, zatem poniższy kod będzie absolutnie prawidłowy:
COnlineDocument* pTutorial = new COnlineDocument("Xion", // autor
"Od zera do gier kodera", // tytuł
"http://avocado.risp.pl"); // URL
PokazDaneDokumentu (pTutorial);
delete pTutorial;
W pierwszej linijce możnaby równie dobrze użyć typu wskazującego na obiekt CDocument, gdyż wskaźnik pTutorial i tak zostanie potraktowany w ten sposób przy przekazywaniu go do funkcji PokazDaneDokumentu().
Efektem jego działania powyższego listingu będzie na przykład taki oto widok:
Brak tu informacji o adresie internetowym dokumentu, ponieważ należy on do składowych specyficznych dla klasy
COnlineDocument. Funkcja
PokazDaneDokumentu() została natomiast stworzona do pracy z obiektami
CDocument, zatem wykorzystuje jedynie informacje zawarte w klasie bazowej. Nie przeszkadza to jednak w przekazaniu jej obiektu klasy pochodnej - w takim przypadku dodatkowe dane zostaną po prostu zignorowane.
To raczej mało satysfakcjonujące rozwiązanie, ale lepsze skutki wymagają już użycia metod wirtualnych. Uczynimy to w kolejnym przykładzie.
Naturalnie, podobny rezultat otrzymalibyśmy podając naszej funkcji obiekt klasy
CBook czy też jakiejkolwiek innej dziedziczącej od
CDocument. Kod procedury jest więc uniwersalny i może być stosowany do wielu różnych rodzajów obiektów.
Eureka! Na tym przecież polega polimorfizm :)
Możliwe że zauważyłeś, iż żadna z tych przykładowych klas nie jest tutaj typem polimorficznym, a jednak podany wyżej kod działa bez zarzutu. Powodem tego jest jego względna prostota. Dokładniej mówiąc, nie jest konieczne sprawdzanie poprawności typów podczas działania programu, bo wystarczająca jest zwykła kontrola, dokonywana zwyczajowo podczas kompilacji kodu.