Polymorfismus (programování)

Polymorfismus je vlastnost programovacího jazyka, objektově orientovaného programování (OOP), která umožňuje:

  • jednomu objektu volat jednu metodu s různými parametry (ad-hoc polymorfismus);
  • objektům odvozeným z různých tříd volat tutéž metodu se stejným významem v kontextu jejich třídy, často pomocí rozhraní;
  • přetěžování operátorů neboli provedení rozdílné operace v závislosti na typu operandů;
  • jedné funkci dovolit pracovat s argumenty různých typů (parametrický polymorfismus, ne ve všech programovacích jazycích).

Rozhodnutí o tom, která metoda bude volána, je u polymorfismu prováděno až za běhu programu (tj. dynamicky pomocí virtuálních funkcí). Tím se odlišuje od přetěžování funkcí, kde je rozhodnutí o volání vhodné funkce provedeno již při překladu (tj. staticky).

Polymorfismus obecněji

Polymorfismus je vlastnost programovacího jazyka, speciálně v objektově orientovaném programování, která umožňuje objektům volání jedné metody se stejným jménem, ale s jinou implementací. V jiném kontextu (než OOP) se tento druh polymorfizmu nazývá podtypový, na rozdíl od parametrického polymorfizmu (ve funkcionálním programování), který odpovídá spíš generice (např. v C#) nebo šabloně v OOP (Java, C++).

Funkce, metoda nebo makro (a další syntaktické konstrukce) se dají volat s různými datovými typy. Implementace se liší podle druhu polymorfizmu.

(Podtypový) Polymorfismus dělíme na dva typy: statický a dynamický. Při statickém je v době překladu znám konkrétní typ argumentu a proto překladač vygeneruje volání konkrétní (monomorfní) funkce, která pracuje se správným typem. Dynamický p. vybírá volanou funkci za běhu (tzv. pozdní vazba, late binding), v OOP typicky pomocí tabulky virtuálních metod (TVM). Tato tabulka není programátorovi přímo přístupná a generuje ji překladač. V obou případech se kód funkcí generuje při překladu. Tzv. přetížené funkce (overloading) odpovídají statickému polymorfizmu, ale pro každý typ parametru/-ů generují samostatný kód (viz omezený polymorfizmus dále). Dynamický polymorfizmus potřebuje (v nějaké formě) informace o typech za běhu, proto má časovou a/nebo paměťovou režii. Statický polymorfizmus typy za běhu (při dobrém návrhu jazyka a implementace) v principu nepotřebuje, ale ze šablon/generik vygeneruje několik podobných funkcí.

Příklady: objektový polymorfismus (s virtuálními metodami) je dynamický, šablony nebo generika jsou statického typu. Dynamicky typované interpretované jazyky (Ruby, Python) používají také dynamický polymorfizmus.

Polymorfismus může být dvojího druhu: univerzální (parametr typu může být jakýkoliv), omezený (typ jen z určitého výčtu). Např. funkce maximum – nemůže být nad čímkoliv, jen nad datovým typem, který lze porovnat na větší, menší. Univerzální typ odpovídá parametrickému polymorfizmu, který lze realizovat jednou funkcí pro všechny typy (za daného omezení). Např. funkce pro délku spojového seznamu nezávisí na typu prvků v seznamu, protože je nevyužívá (a např. prvky jsou schované za pointrem). Omezený polymorfizmus závisí na typu a konkrétní typ si funkce předává jako dodatečný (schovaný) parametr, TVM je realizovaná tímto způsobem.

Pokud si chceme realizovat polymorfizmus (ve formě přetížení) sami, např. při variantních záznamech (union v C), např. pro dvě reprezentace komplexních čísel (pravoúhlou a polární), můžeme ve funkcích využít vnitřně přepínač switch podle tagu reprezentace.

Že je oblast populární a terminologie nejednotná, můžete zjistit porovnáním částí z dědičnosti, přetížení funkce, metoda a dalších.

Příklady

Parametrický polymorfismus

Příkladem parametrického (operátorového) polymorfismu jsou šablony v C++. Funkce nemá pevně stanovený typ argumentu, dokáže tak přijmout různé typy. Následující funkce násobí parametry i přesto, že nezná jejich typ, lze ji tak volat s různými typy:

#include <iostream>

using namespace std;
template <class T>
T multiply(T x, T y)
{
	return x * y;
}

int main()
{
	int res1, x1 = 2, y1 = 4;
	res1 = multiply(x1, y1);

	unsigned long res2, x2 = 5, y2 = 10;
	res2 = multiply(x2, y2);

	cout << res1 << "\n" << res2;

	return 0;
}

Rozhraní

Mějme v jazyce PHP rozhraní ITvar definující metodu obsah(), od které je očekáváno, že bude vracet obsah geometrického útvaru. Dále třídy Trojuhelnik a Kruh implementující rozhraní ITvar. Obě třídy implementují metodu obsah() z rozhraní ITvar, ale každá odlišným způsobem. Nakonec definujme funkci vypisInfo(), která bude v parametru očekávat instanci třídy implementující rozhraní ITvar:

interface ITvar
{
	public function obsah();
}

class Trojuhelnik implements ITvar
{
	private $a, $b, $c;

	public function __construct($a, $b, $c)
	{
		$this->a = $a;
		$this->b = $b;
		$this->c = $c;
	}

	public function obsah()
	{
		//výpočet obsahu pomocí Heronova vzorce
		$o = ($this->a + $this->b + $this->c) / 2;
		return sqrt($o * ($o - $this->a) * ($o - $this->b) * ($o - $this->c));
	}
}

class Kruh implements ITvar
{
	private $polomer;

	public function __construct($polomer)
	{
		$this->polomer = $polomer;
	}

	public function obsah()
	{
		return M_PI * $this->polomer * $this->polomer;
	}
}

function vypisInfo(ITvar $tvar)
{
	echo 'Instance třídy ', get_class($tvar), ', obsah: ', round($tvar->obsah(), 2);
}

vypisInfo(new Trojuhelnik(5, 10, 12)); //Instance třídy Trojuhelnik, obsah: 24.54
echo PHP_EOL;
vypisInfo(new Kruh(10)); //Instance třídy Kruh, obsah: 314.16

Přetěžování operátorů

Podrobnější informace naleznete v článku Přetížení operátoru.

PHP interpretuje operátor + jako sčítání (jsou-li operandy čísla) nebo jako sloučení polí (jsou-li operandy pole):

$n1 = 5;
$n2 = 4;
$a1 = array('jablka' => 8, 'hrusky' => 5);
$a2 = array('svestky' => 2);

// operátorový polymorfismus
var_dump($n1 + $n2); // int 9
var_dump($a1 + $a2); // array('jablka' => 8, 'hrusky' => 5, 'svestky' => 2)

Související články