====== Objektové programování (OOP) ====== MervisIDE dovoluje používat některé z objektových vlastností používaných v jazycích jako je Java, .NET nebo C++. Základním typem pro využívání těchto vlastností je funkční blok. Je možné využívat následující konstrukty: * Zapouzdření * Metody, včetně virtuálních metod * Odkazovat se na předka (SUPER) * Hierarchické struktury a dědění * Definovat a implementovat rozhraní (interfaces) * Polymorfismus Všechny zvyklosti (RETAIN, INPUT, OUTPUT, INOUT, ...) jsou shodné s klasickým použitím funkčního bloku. ====== Motivační příklad - counter ====== Následující příklad ukazuje implementaci jednoduchého čítače, který má maximum, minimum, možnost čítat nahoru nebo dolu a umí vracet aktuálně načítanou hodnotu: FUNCTION_BLOCK CCounter VAR m_iCurrentValue: INT; m_bCountUp: BOOL:=TRUE; END_VAR VAR m_iUpperLimit: INT:=+10000; m_iLowerLimit: INT:=-10000; END_VAR METHOD GetCount : INT GetCount := m_iCurrentValue; END_METHOD METHOD Count IF (m_bCountUp AND m_iCurrentValuem_iLowerLimit) THEN m_iCurrentValue:= m_iCurrentValue-1; END_IF; END_METHOD METHOD SetDirection VAR_INPUT bCountUp: BOOL; END_VAR m_bCountUp:=bCountUp; END_METHOD END_FUNCTION_BLOCK Použití takto definovaného bloku je v rámci deklarace stejné jako u jakéhokoliv jiného bloku, včetně explicitní inicializace. Jednotlivé metody se pak volají následujícím způsobem: PROGRAM main VAR counter1 : CCounter; counter2 : CCounter := (m_iUpperLimit := 15000, m_iLowerLimit := -10); current_value : int; END_VAR counter1.SetDirection(bCountUp := true); counter1.Count(); current_value := counter1.GetCount(); counter2.Count(); END_PROGRAM ====== Metody funkčního bloku ====== Koncept metod v MervisIDE je stejný jako u jiných objektově orientovaných jazyků. Metody definují operace v rámci jednotlivých instancí funkčního bloku a operují nad daty dostupnými instanci funkčního bloku. Deklarace metody je uzavřena mezi klíčovými slovy **METHOD** a **END_METHOD**. Metody lze přirovnat k funkcím. Stejným způsobem se jim definuje jméno, parametry (vstupní, výstupní, lokální) a jejich návratová hodnota. Kromě jejich vlastních parametrů mohou pracovat nad společnými daty funkčního bloku. V jednom funkčním bloku může být více metod a metody mohou volat další metody. V současné verzi neexistuje podpora pro práci s metodami v grafických jazycích. Je možné je využívat pouze v jazyku ST. Implementace metod v MervisIDE má podporu pro EN a ENO. ====== Dědičnost ====== Koncept dědičnosti v MervisIDE je stejný jako u jiných objektově orientovaných jazyků. Lze definovat hierarchii funkčních bloků, kde zpravidla předek představuje obecnější strukturu a potomek nějakou konkretizaci. MervisIDE nepodporuje vícenásobnou dědičnost. Podporuje však možnost implementovat více rozhraní (interfaces). Viz dále. Hierarchický vztah se definuje klíčovým slovem **EXTENDS**. Př.: FUNCTION_BLOCK Circle EXTENDS GeomShape. Potomek (odděděný funkční blok) má k dispozici všechny proměnné a metody funkčního bloku, ze které dědí. K této funkčnosti může přidávat další metody a data. ====== Virtuální metody ====== Virtuální metody jsou takové metody, kterým je možné v hierarchii funkčních bloků měnit implementaci. Pokud se metoda zavolá na instanci předka, bude zavolána odpovídající implementace potomka. Například v hierarchii máme základní funkční blok GeometryShape, který definuje metodu "Area" na výpočet obsahu geometrického obrazce. Virtuální metoda musí být označena klíčovým slovem **VIRTUAL** a stejně označené metody v potomcích musí toto slovo uvádět také. Není možné změnit typ metody. "Virtuálnost" musí odpovídat předkovi, a to až na úroveň první deklarace. Jako příklad uvidíme konkrétní hierarchii geometrických obrazců s obdélníkem a kruhem a u nich implementaci odpovídajícího matematického vzorce. FUNCTION_BLOCK GeomShape (* abstracktni metoda predka *) METHOD VIRTUAL Area : REAL END_METHOD; END_FUNCTION_BLOCK FUNCTION_BLOCK Rectangle EXTENDS GeomShape VAR_INPUT x, y : real; END_VAR (* konkretni implementace potomka *) METHOD VIRTUAL Area : REAL Area := x * y; END_METHOD; END_FUNCTION_BLOCK FUNCTION_BLOCK Circle EXTENDS GeomShape VAR_INPUT radius : real; END_VAR (* konkretni implementace potomka *) METHOD VIRTUAL Area : REAL Area := 3.14 * radius * radius; END_METHOD; END_FUNCTION_BLOCK Využití je pak možné následovné: PROGRAM geom_shapes VAR rect1 : Rectangle := (x := 10, y := 20); circle1 : Circle := (radius := 10); shape : REF_TO GeomShape; rect_area, circle_area : REAL; END_VAR (* obecny predek, kteremu muzeme priradit referenci na jakehokoliv potomka *) shape := REF rect1; (* shape nyni predstavuje Rectangle -> spocita obsah obdelniku *) (* rect_area bude po provedeni mit hodnotu 200 *) rect_area := shape.Area(); shape := REF circle1; (* shape nyni predstavuje Circle -> spocita obsah kruhu *) (* circle_area bude po provedeni mit hodnotu 314 *) circle_area := shape.Area(); END_PROGRAM ====== Přístup k vlastním položkám a položkám předků ====== Občas je potřeba, aby potomek přistoupil na položku předka nebo naopak vynutil přístup ke své položce. K tomu slouží klíčová slova **SUPER** (přístup k předkovi) a **THIS** (přístup k vlastní položce). V následujícím příkladu existuje abstraktní předek Car s potomkem StreetCar, který má potomka Bmw. V metodě Description lze zjistit popis předka a spojit ho s vlastním popisem. FUNCTION_BLOCK Car METHOD VIRTUAL Description : STRING END_METHOD END_FUNCTION_BLOCK FUNCTION_BLOCK StreetCar EXTENDS Car METHOD VIRTUAL Description : STRING Description := "STREET CAR"; END_METHOD END_FUNCTION_BLOCK FUNCTION_BLOCK Bmw EXTENDS StreetCar METHOD VIRTUAL Description : STRING (* vrati text od predka a prida k nemu svuj *) (* Vysledkem metody tedy bude "STREET CAR BMW" *) Description := CONCAT(SUPER.Description(), " BMW"); END_METHOD END_FUNCTION_BLOCK Další příklad: FUNCTION_BLOCK Base METHOD VIRTUAL Description : STRING Description := "Base"; END_METHOD END_FUNCTION_BLOCK FUNCTION_BLOCK Descendant EXTENDS Base METHOD VIRTUAL Description : STRING Description := "Descendant"; END_METHOD METHOD SuperDescription : STRING (* Přesně specifikujeme, že jde o konkrétní metodu na předkovi. *) SuperDescription := SUPER.Description(); END_METHOD METHOD ThisDescription : STRING (* THIS se nemusí uvádět. Implicitně se předpokládá jako nejbližší scope. *) ThisDescription := THIS.Description(); END_METHOD END_FUNCTION_BLOCK PROGRAM this_super_prg VAR fb : Descendant; desc, this_desc, super_desc : STRING; END_VAR desc := fb.Description(); // returns "Descendant" this_desc := fb.ThisDescription(); // returns "Descendant" super_desc := fb.SuperDescription(); // returns "Base" END_PROGRAM ====== Vlastní kód výpočtu funkčního bloku ====== Klasický výpočet (tj. bez objektových rozšíření) funkčního bloku si lze v rámci metod představit jakožto metodu "main". Nemá vstupní parametry ani výstupní parametry a pracuje nad daty dostupnými z kódu výpočtu. Tento kód je také možné pojmout jako virtuální. A to označením celého funkčního bloku jakožto virtuálního. Př.: FUNCTION_BLOCK VIRTUAL ScheduleBase ====== Rozhraní - Interface ====== Koncept rozhraní umožňuje oddělit implementaci a specifikaci vlastností, které musí implementace splňovat. Interface obsahuje množinu prototypů metod a neobsahuje jakékoliv datové položky a proměnné. Definice rozhraní je ohraničena klíčovými slovy **INTERFACE** a **END_INTERFACE**. Užití interface je možné v definici funkčního bloku, což pak znamená, že funkční blok musí všechny metody předepsané interfacem implementovat. Na toto slouží klíčové slovo **IMPLEMENTS**. Druhé možné využití je specifikace typu proměnné. Do proměnná typu interface pak mohou být přiřazeny funkční bloky, které samy nebo v rámci nějakého předka, implementují interface. Každý funkční blok může implementovat libovolné množství interface. V následujícím příkladu existuje interface Room, který definuje metody, které se mají volat běhen dne a během noci. Tento interface je implementován funkčním blokem LightRoom, který reprezentuje pokoj s žárovkou. Během dne ji vypíná, v noci zapíná. INTERFACE Room METHOD DayTime END_METHOD // Called in day-time METHOD NightTime END_METHOD // in night-time END_INTERFACE FUNCTION_BLOCK LightRoom IMPLEMENTS Room VAR Light: BOOL; END_VAR METHOD DayTime Light:= FALSE; END_METHOD METHOD NightTime Light:= TRUE; END_METHOD END_FUNCTION_BLOCK Využití v programu: PROGRAM light_rooms VAR kitchen : LightRoom; room_interf : REF_TO Room; END_VAR; // class instantiation room_interf := REF kitchen; room_interf.DayTime(); END_PROGRAM ====== Dědění interface ====== Dědění interface je obdobné jako dědění funkčních bloků. Také využívá klíčového slova **EXTENDS**. Stejně tak interface nemůže dědit více interface. Funkční blok, který implementuje děděný nebo děděné interface musí implementovat sjednocení metod ze všech interface. Příklad: INTERFACE BaseA METHOD A END_METHOD END_INTERFACE INTERFACE BaseB METHOD B END_METHOD END_INTERFACE INTERFACE BaseC EXTENDS BaseA METHOD C END_METHOD END_INTERFACE INTERFACE BaseD METHOD D END_METHOD END_INTERFACE FUNCTION_BLOCK C IMPLEMENTS BaseC, BaseD METHOD A END_METHOD METHOD B END_METHOD METHOD C END_METHOD METHOD D END_METHOD END_FUNCTION_BLOCK ====== Polymorfismus ====== Již z předešlého textu a příkladů je vidět, že v různých situacích může vystupovat předem neznámý typ. Až v době běhu programu dochází k aplikaci konkrétní instance. Toho lze velmi výhodně využít v zabstraktňování problému. Obecný algoritmus může řešit vše nad abstraktními typy a konkrétní implementace pak mohou být poskytovány i zcela jiným subjektem, než je tvůrce knihovny. Polymorfismus lze využít v následujících případech: * **Interface** - jelikož interface nijak nespecifikuje implementaci, ale pouze kontakt s okolím, je vhodný pro oddělení algoritmů a implementace. Dobrým příkladem z jsou třeba transformace IO. GUI a celý systém předpokládá, že existuje interface definující transformace. Uživatel si pak může udělat libovolnou implementaci pro svá zařízení (př. různé rozsahy teploměrů apod.) * **Hierarchie funkčních bloků** - velmi podobné využití interface. Předkové mohou sloužit v obecnějších voláních a knihovnách podobně jako interface. * **Virtuální metody** - v případě volání na **THIS** je i v případě, že vlastní instance je předkem, volána vždy až metoda na posledním potomkovi. * **REF_TO** - do proměnných je možné přiřazovat odvozený funkčního bloku nebo interface (viz příklady).