====== 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).