Hallo,
allgemein finde ich die OOP-Erweiterungen der IEC sehr hilfreich. ich habe mir zum Beispiel einen Logger gebaut der aus einer C#-Komponente (GUI) und einer SPS-Komponente (Lib mit mehreren FBs, alles mit OOP) besteht.
Unter anderem habe ich in der Lib auch einen Basis-FB definiert der die gewünschten Logger-Funktionen zur verfügung stellt. Wenn ich jetzt in der eigentlichen Applikation einen FB schreiben möchte, der loggen können soll, so muss ich den nur noch von dem Basis-FB ableiten (erweitern) und der FB hat automatisch die Logger-Funktionen mit drinnen.
Hinzu kommen bei der OOP nette Funktionen, wie z.B. dass man mit Properties einen Schreibschutz herstellen kann und direkte Skalierungen und Bereichsüberwachungen realisieren kann.
Wichtig ist, dass man versteht, dass man nicht die gesammte Applikation in OOP umsetzten muss. Hinzu kommt, dass man die OOP-Erweiterung in allen Sprachen verwenden kann (KOP, FUP, AWL, ...).
Ich kenne es von eineigen Firmen so, da werden die Kernfunktionen in ST (teilweise schon mit OOP) erstellt und in eine Lib gekapselt. In den höheren Ebenen wird weiterhin KOP oder FUP verwendet, damit die Instandhaltung und Inbetriebnahme leichter arbeiten kann (und das meine ich jetzt nicht negativ oder abwertend!!!).
Klar kann ich durch die Verwendung der OOP-Erweiterungen sehr unübersichtliche und verkopfte Programme erstellen. Hier muss man einfach aufpassen, dass man das ganze nicht zu weit treibt. Zum Beispiel halte ich auch nichts davon, dass man von einem FB mehr als 3-4 Vererbungsstufen erstellt. Aber auch mit der konvenstionellen Progammierung kann ich einen grausamen Programmierstil pflegen ;-).
Die Pflege des Programmcodes finde ich bei der Verwendung von OOP deutlich einfacher, da die Funktionen besser gekapselt werden können und man so schneller den Ort findet, wo etwas zu verbessern/berichtigen ist.
Es gibt aber auch deutliche Nachteile. So ist das Debuggen eines Programms welches mit OOP erstellt wurde (Verwendung von Interfaces, Ableitung, etc.) mit TwinCAT viel aufwändiger. Wenn man da mit dem Debugger schrittweise durch das Programm steppt, hat man rucki zucki mehr als 20 Fenster offen. Bei Interfaces kommt hinzu, dass man hier ja quasi immer mit Pointern arbeietet, was die Sache weiter erschweren kann (nach dem Motto: ist das jetzt überhaupt die richtige Instanz die ich da sehe
).
Um es kurz zu machen:
Ich finde die OOP-Erweiterung der IEC durchaus lobenswert. Es muss halt zum eigenen (und dem der Firma) Programmiersilt und der Aufgabenstellung passen. Man muss sich bei der Einarbeitung in das Thema selbst ein bisschen mehr Zeit zugestehen (auch wenn die wie immer Mangelware ist). Wenn man auf OOP umsteigen möchte muss man es nicht sofort allumfänglich machen, sondern man kann partiel anfangen. Und was auch sehr wichtig ist, man sollte alle Programmierer mitziehen (und unterstüzen, aller Anfang ist schwer)!
Einen Aspekt sollte man auch nicht vernachlässigen:
Bei der Verwendung von OOP geht es eigentlich immer um eine bessere Wiederverwendbarkeit des Codes. Dabei wird hüfig außer acht gelassen, dass man die anderen Gewerke (Mechanik, Elektrik, ...) außen vor lässt.
Dabei wäre es Sinnvoll mit diesen Gewerken zusammen die Einteilung in Objekte vorzunehmen und darauf zu achten, dass diese Objekte auf allen Ebenen/Gewerken die gleichen Schnittstellen verwenden/beschreiben!
bzgl. Konstruktoren und Destruktoren:
In TwinCAT gibt es die Möglichkeit an einem FB Methoden hinzuzufügen. Hier kann man bei dem Dialog statt dem Namen der Methode über das Dropdown Menü auch die vordefinierten "FB_init", "FB_reinit" und "FB_exit" -Methoden auswählen.
Alle drei Methoden werden vom System sowieso immer verwendet, auch wenn sie nicht explizit hinzugefügt wurden (dann sind sie halt leer). Durch hinzufügen dieser Methonden hat man dann nun die Möglichkeit selbst zu entscheiden, was den bei den jeweiligen Schritten unternommen werden soll. Z.B. lassen sich so Initialisierungsparameter an eine FB-Instanz übergeben, die für dem ersten wirklichen Aufruf des FBs schon gesetzt sind.
Beispiel:
ein FB hat eine lokale Variable die zu beginn einen Wert haben soll:
Code:
FUNCTIONBLOCK FB_Test
VAR[INDENT]nInitValue : int;
[/INDENT]
END_VAR
bei der FB_init-Methode kann man jetzt für diese Variable eine Input-Variable erstellen (hier "nInitParam") der den Wert auf die Variable im FB schreibt:
Code:
METHOD FB_init : BOOL
VAR_INPUT
bInitRetains : BOOL; // if TRUE, the retain variables are initialized (warm start / cold start)
bInCopyCode : BOOL; // if TRUE, the instance afterwards gets moved into the copy code (online change)
nInitParam : INT;
END_VAR
//---------
nInitValue := nInitParam;
Wenn man jetzt im Programm eine Instanz des FBs anlegt
muss man auch die Initialisierungsparameter mit übernehmen:
Code:
PROGRAMM MAIN
VAR[INDENT]fbTest : FB_Test(nInitParam := 5);
[/INDENT]
END_VAR
Wichtig: die Klammer mit den Initialisierungsparameter kommt dierekt nach dem FB-Namen, es handelt sich hierbei nicht um die Angabe von klassichen Initialisierungswerten die ja mit dem Zuweisungsoperator
=) erfolgen würden!
Nachteil hierbei ist zum einen, dass ich den Parameter immer angeben muss (sonst schimpft der Kompiler) und zum Andern ist eine Kapselung schwiriger wie ich im Post oben beschrieben habe. Außerdem ist ein Debuggen der Methoden so nicht möglich.
Hier gibt es als Alternative die Verwendung von dem Attribut "call_after_init" bei dem ich den Zeitpukt selbst definieren kann.
Anbei ein Beispiel, dass das ganz gut verdeutlicht
(die Dateierweiterung muss von .zip einmal in .tszip (TwinCAT) umbenannt werden)...
Anhang anzeigen Init_Sample.zip
Viele Grüße