/ janturon.cz / Od kodéra k analytikovi / Polymorfismus místo testování typu

Polymorfismus místo testování typu

RTTI

C++ má nástroj RTTI (Runtime Type Information) nahrazující operátor přetypování šablonovými funkcemi:

Javascriptová alternativa je operátor instanceof, který vrací true, jestliže je objekt na levé straně instancí (byť zděděné) třídy na pravé straně.

Špatný přístup

typeid bývá v jiných objektových jazycích nahrazen třídou Reflection vracející podrobnější informace o objektech a třídách. Tak jako tak ale tyto nástroje svádí ke špatným zvyklostem, mezi nimiž je nejznámější porovnávání typu, které lze téměř všude nahradit polymorfismem. Mějme jednoduchý kód využívající hierarchii tříd:

// C++ struct Human { string Name; Human(string name) { Name = name; } }; struct Woman : Human { Woman(string name) : Human(name) { } }; struct Man : Human { Man(string name) : Human(name) { } }; string Info(Human* h) { if(typeid(Man)==typeid(*h)) return "Man"; if(typeid(Woman)==typeid(*h)) return "Woman"; return "Unknown"; } int main() { Man* bob = new Man("Bob"); Woman* ann = new Woman("Ann"); cout << Info(bob) << ' ' << Info(ann); } // Javascript function Human(name) { this.Name = name; }; function Woman() { Human.call(this,name); }; function Man() { Human.call(this,name); }; function Info(h) { if(h instanceof Man) return "Man"; if(h instanceof Woman) return "Woman"; return "Unknown"; } var m = new Man(), w = new Woman(); alert(Info(m)); alert(Info(w));

Výstupem je sice podle očekávání Man Woman, ale kód je náchylný k chybám: kdybychom potřebovali vytvořit ještě třídy Boy a Girl, museli bychom vědět (a pamatovat si), že je nutno upravit i funkci Info(). Opomenutí se neprojeví chybou při kompilaci a možná ani za běhu. Požadavky na více takových znalostí v rozsáhlém kódu spolu s nejistotou chyby jsou nepřijatelné.

Dobrý přístup

Nedostatek možného přehlédnutí chyby lze odstranit pomocí vyjímek:

// C++ class InfoException: public exception { virtual const char* what() const throw() { return "Human not recognized"; } } infoException; string Info(Human* h) { if(typeid(Man)==typeid(*h)) return "Man"; if(typeid(Woman)==typeid(*h)) return "Woman"; throw infoException; } // Javascript function Info(h) { if(h instanceof Man) return "Man"; if(h instanceof Woman) return "Woman"; throw "Human not recognized"; }

Chyba se ale i přesto nemusí projevit. Vhodnější a zaručené řešení je využití polymorfismu, kde funkčnost metody Info() přesuneme do odvozených tříd, zatímco do základní třídy ji zapíšeme jako ryze virtuální. Kompilátor pak upozorní na opomenutí už v době kompilace.

// C++ struct Human { string Name; Human(string name) { Name = name; } virtual string Info()=0; }; struct Woman : Human { Woman(string name) : Human(name) { } string Info() { return "Woman"; } }; struct Man : Human { Man(string name) : Human(name) { } string Info() { return "Man"; } }; string Info(Human* h) { return h->Info(); } int main() { Man* bob = new Man("Bob"); Woman* ann = new Woman("Ann"); cout << Info(bob) << ' ' << Info(ann); } // Javascript function Human(name) { this.Name = name; this.Info = function() { throw "Human not recognized"; } }; function Woman() { Human.call(this,name); this.Info = function() { return "Woman"; } }; function Man() { Human.call(this,name); this.Info = function() { return "Man"; } }; var m = new Man(), w = new Woman(); alert(m.Info()); alert(w.Info());