Je speciálním případem, je druhem, je typem, anglicky is-a je v ontologii a reprezentaci znalostí[pozn. 1], v objektově orientovaném programování a objektově orientované analýze a návrhu je subsumpční[pozn. 2] vztah mezi abstrakcemi (například, typy, třídami), kdy jedna třída A je podtřídou jiné třídy B (a tedy B je nadtřídou třídy A). Jinými slovy typ A je podtypem typu B, jestliže specifikace A implikuje specifikaci B. Tj. libovolný předmět (nebo třída), který vyhovuje specifikaci A, vyhovuje také specifikaci B, protože specifikace B je slabší.[1]
Například kočka je speciálním případem zvířete, ale ne naopak. Všechny kočky jsou zvířata, ale ne všechna zvířata jsou kočky. Chování, které je relevantní pro všechna zvířata, je definováno na třídě zvířat, zatímco chování, které je relevantní pouze pro kočky, je definováno na třídě koček. Definováním třídy koček jako specializace třídy zvířat všechny kočky zdědí chování definované pro zvířata, aniž by bylo nutné toto chování pro kočky explicitně uvádět.
Vztah je speciálním případem (is-a je třeba rozlišovat od vztahu mít něco (has a mezi typy (třídami); zaměňování těchto dvou vztahů je častou chybou při navrhování modelu (například počítačového programu) skutečného vztahu mezi objektem a jeho podřízeným. Vztah je speciálním případem se také liší od vztahu je instancí mezi objekty (instancemi) a typy (třídami): viz Rozlišování typu a entity.
Shrneme-li vztahy, pak jsou to:
Podtypování umožňuje nahradit jeden typ jiným typem nebo abstrakci. Říkáme, že podtypování vytváří vztah je speciálním případem mezi podtypem a nějakou existující abstrakcí, a to buď implicitně anebo explicitně, v závislosti na podpoře jazyka. V jazycích podporujích dědičnost jako mechanismus podtypování lze tento vztah vyjádřit explicitně prostřednictvím dědičnosti.
Následující kód v jazyce C++ vytváří explicitní vztah dědičnosti mezi třídami B a A, kde B je jak podtřídou i tak podtypem třídy A, a může být použita jako třída A všude tam, kde je uvedena třída B (prostřednictvím reference, ukazatele nebo samotného objektu).
class A
{ public:
void DoSomethingALike() const {}
};
class B : public A
{ public:
void DoSomethingBLike() const {}
};
void UseAnA(A const& some_A)
{
some_A.DoSomethingALike();
}
void SomeFunc()
{
B b;
UseAnA(b); // b can be substituted for an A.
}
Následující kód v jazyce Python vytváří explicitní vztah dědičnosti mezi třídami B and A, kde B je jak podtřídou i tak podtypem třídy A, a může být použita jako třída A všude tam, kde je vyžadována třída B.
class A:
def do_something_a_like(self):
pass
class B(A):
def do_something_b_like(self):
pass
def use_an_a(some_a):
some_a.do_something_a_like()
def some_func():
b = B()
use_an_a(b) # b can be substituted for an A.
V následujícím příkladě je type(a) „běžný“ typ a type(type(a)) je metatyp. Ačkoli všechny typy, tak jak jsou distribuované, mají stejný metatyp PyType_Type, který je zároveň vlastním metatypem), není to podmínkou. Typ klasických tříd, známý jako types.ClassType, lze také považovat za samostatný metatyp.[4]
>>> a = 0
>>> type(a)
<type 'int'>
>>> type(type(a))
<type 'type'>
>>> type(type(type(a)))
<type 'type'>
>>> type(type(type(type(a))))
<type 'type'>
V jazyce Java se vztah je speciálním případem mezi typovými parametry jedné třídy nebo rozhraní a typovými parametry jiné třídy určuje pomocí klauzulí extends a implements.
Pomocí tříd Collections
implementuje ArrayList<E>
třídu List<E>
, a List<E>
rozšiřuje Collection<E>
. ArrayList<String>
je tedy podtypem typu List<String>
, který je podtypem typu Collection<String>
. Vztah podtypování se mezi typy zachovává automaticky. Při definici rozhraní PayloadList
, které ke každému prvku přiřazuje volitelnou hodnotu generického typu, může jeho deklarace vypadat takto:
interface PayloadList<E, P> extends List<E> {
void setPayload(int index, P val);
...
}
Následující parameterizace typu PayloadList jsou podtypy typu List<String>
:
PayloadList<String, String>
PayloadList<String, Integer>
PayloadList<String, Exception>
Liskovové princip zastoupení (LSP) vysvětluje vlastnost „Jestliže pro každý objekt o1 typu S existuje objekt o2 typu T takový, že pro všechny programy P definované v termínech T se chování P nezmění, když je o1 nahrazeno o2, pak S je podtypem T“,[5]
Následující příklad ukazuje narušení Liskovové principu zastoupení.
class Rectangle
{
public:
void SetWidth(double w) { itsWidth = w; }
void SetHeight(double h) { itsHeight = h; }
double GetHeight() const { return itsHeight; }
double GetWidth() const { return itsWidth; }
double GetArea() const { return GetHeight() * GetWidth(); }
private:
double itsWidth;
double itsHeight;
};
Z programátorského hlediska lze třídu Square implementovat děděním z třídy Rectangle.
public class Square : Rectangle
{
public:
virtual void SetWidth(double w);
virtual void SetHeight(double h);
};
void Square::SetWidth(double w)
{
Rectangle::SetWidth(w);
Rectangle::SetHeight(w);
}
void Square::SetHeight(double h)
{
Rectangle::SetHeight(h);
Rectangle::SetWidth(h);
}
To však porušuje LSP, přestože mezi obdélníkem Rectangle a čtverecem Square platí vztah je speciálním příkadem.
Uvažujme následující příklad, kde funkce g nefunguje, pokud je předán Čtverec, a tak by se dalo uvažovat o porušení principu otevřeného-uzavřeného.
void g(Rectangle& r)
{
r.SetWidth(5);
r.SetHeight(4);
assert(r.GetArea()) == 20); // assertion will fail
}
Opačně, pokud uvažujeme, že typ tvaru by měl být pouze omezením na vztah jeho rozměrů, pak předpoklad v g(), že SetHeight změní výšku a plochu, ale ne šířku, který je neplatný, ne pouze pro skutečné čtverce, ale dokonce potenciálně i pro jiné obdélníky, které by mohly být kódovány tak, aby při změnách výšky zachovávaly plochu nebo poměr stran.
V tomto článku byl použit překlad textu z článku is-a na anglické Wikipedii.