Wie ich schon in meinem letzten Beitrag schrieb, wurde in der neuen MATLAB-Version R2008a die Art und Weise der objektorientierten Programmierung mit der Programmiersprache m verändert. Dazu wurde eine völlig neue Syntax definiert, die im folgenden vorgestellt werden soll.
Bis Version R2007b war es notwendig, für eine Klasse ein neues Verzeichnis zu erzeugen, dessen Name mit einem @ beginnt. Diese Vorgehensweise kann auch weiterhin verwendet werden, und zwar immer dann, wenn die Methoden der Klasse auf mehrere Dateien verteilt werden soll. Ansonsten kann eine Klasse nun auch in einer einzelnen Datei untergebracht haben, die nach dem Bezeichner der Klasse benannt wird. Als Beispiel soll eine Klasse für ein Testergebnis dienen. Der Name der Klasse ist "test_result", dementsprechend wird eine Datei mit dem Namen "test_result.m" erstellt:
classdef test_result
end
Die Anweisung "classdef" leitet die neue Klasse ein, es folgt der Name des Klasse und in üblicher MATLAB-Manier wird der Block mit einem "end" beendet. Mit dieser simplen Konstruktion ist die Klasse bereits funktionsfähig und kann auf der Kommandozeile instanziiert werden (vorausgesetzt, die Datei "test_result.m" ist entweder im aktuellen Verzeichnis oder im MATLAB-Pfad auffindbar):
>> result = test_result
result =
test_result with no properties.
list of methods
Wie MATLAB richtig bemerkt, hat die Klasse noch keine Eigenschaften. Das Wort "methods" wird hingegen als Link dargestellt und klickt man darauf, erscheint folgende Ausgabe mit dem Standardkonstruktor der Klasse:
Methods for class test_result:
test_result
Zur Definition einer Eigenschaft wurde der Block "properties" eingeführt, für Methoden der Block "methods":
classdef test_result
properties
should_stop = 0;
end
methods
function should_stop = get_should_stop(self)
should_stop = self.should_stop;
end
end
end
Im Beispiel wird die Eigenschaft "should_stop" definiert, die innerhalb des Testergebnisses anzeigt, dass die Testausführung beendet werden soll. Auf die Eigenschaft kann mittels der Methode "get_should_stop" zugegriffen werden.
Wie schon in R2007b und allen vorherigen Versionen, müssen bei der Veränderung einer Klasse alle Objekte dieser Klasse aus dem Workspace entfernt werden. Immerhin reicht aber jetzt ein einfaches "clear" statt dem bisherigen "clear all":
>> clear
>> result = test_result
result =
test_result
properties:
should_stop: 0
list of methods
Im Gegensatz zur alten Programmierweise ist es nun auch erlaubt, direkt auf die Eigenschaften zuzugreifen:
>> result.should_stop
ans =
0
Soll ein solcher Zugriff unterbunden werden, kann der "properties"-Block mit einem zusätzlichen Attribut versehen werden:
classdef test_result
properties (GetAccess = private)
should_stop = 0;
end
methods
function should_stop = get_should_stop(self)
should_stop = self.should_stop;
end
end
end
Damit wird der Zugriff auf "should_stop" als privat deklariert, alternativ ist auch das Schlüsselwort "protected" erlaubt, welches den Zugriff auf ein Paket beschränkt (siehe unten). Das ganze führt zu einem interessanten Effekt:
>> clear
>> result = test_result
result =
test_result with no properties.
list of methods
Laut dieser Aussage hat test_result nun keine Eigenschaften mehr. Eine Lesezugriff auf "should_stop" ist nur noch über die definierte Methode möglich:
>> result.should_stop
??? Getting the 'should_stop' property of the 'test_result' class is not allowed.
>> result.get_should_stop
ans =
0
Ein Schreibzugriff ist aber weiterhin möglich:
>> result.should_stop = 1;
>> result.get_should_stop
ans =
1
Soll auch dieser verhindert werden, muss auch das Attribut "SetAccess" entsprechend auf "private" gesetzt werden.
classdef test_result
properties (GetAccess = private, SetAccess = private)
should_stop = 0;
end
methods
function should_stop = get_should_stop(self)
should_stop = self.should_stop;
end
end
end
Neben "GetAccess" und "SetAccess" existieren diverse weitere Attribute, u.a. um Eigenschaften als Konstanten zu definieren (sprich als statische Eigenschaften) oder um diese von der Serialisierung auszuschließen. Will man Eigenschaften mit verschiedenen Attributen auszeichnen, fügt man einfach mehrere "properties"-Blöcke ein:
classdef test_result
properties
errors = {};
end
properties (GetAccess = private, SetAccess = private)
should_stop = 0;
end
methods
function should_stop = get_should_stop(self)
should_stop = self.should_stop;
end
end
end
Ferner ist es möglich, spezielle Methoden für den Zugriff auf eine Eigenschaft zu erstellen. Dies ist insbesondere dann sinnvoll, wenn vor dem Zuweisen oder Lesen weitere Schritte ausgeführt werden sollen, beispielsweise eine Prüfung des Wertebereichs.
Auch bei Methoden hat sich einiges getan. Oben wurde bereits auf die Methode "get_should_stop" mit der neuen Syntax verwendet, es ist aber auch weiterhin die alte Variante möglich:
>> get_should_stop(result); % alt
>> result.get_should_stop; % neu
Ebenfalls im "method"-Block kann der Konstruktor der Klasse definiert werden, wobei die gleiche Syntax wie bei einer normalen Funktion verwendet wird. Der Name der Funktion muss dem Namen der Klasse entsprechen, der Rückgabewert muss stets eine Instanz der Klasse sein.
Will man explizit eine Methode der jeweiligen Superklasse aufrufen, d.h. einer Klasse, von der die verwendete Klasse per Vererbung abgeleitet ist, muss dem Namen der Methode ein @ und dann der Name der Superklasse folgenden. Beispiel:
classdef text_test_result < test_result
methods
function should_stop = get_should_stop(self)
fprintf(1, 'Should stop.\n');
should_stop = get_should_stop@test_result(self);
end
end
end
Die Klasse "text_test_result" wird hier von der Klasse "test_result" abgeleitet. Die Methode "get_should_stop" gibt zunächst eine Meldung auf der Standardausgabe aus, und ruft dann selbige Methode der Superklasse auf.
Wie auch bei den Eigenschaften lassen sich neuerdings zusätzliche Attribute zu jedem "method"-Block hinzufügen, die den Zugriff auf die enthaltenen Methoden limitieren können sowie sie als statisch, abstrakt oder endgültig (MathWorks nennt das "sealed", bei Java würde man "final" schreiben) markieren.
Schließlich können Klassen in Paketen organisiert werden. Dazu muss die Klassendatei in ein Verzeichnis verschoben werden, dessen Name mit einem + beginnt und nachfolgend dem Namen des Paketes entspricht. Verschiebt man die Beispieldatei "test_result.m" in das Verzeichnis "+mlunit", welches außerdem dem MATLAB-Pfad hinzugefügt wird, so gehört die Klasse "test_result" nun dem Paket "mlunit" an. Entsprechend wird sie über folgenden Aufruf verwendet:
>> result = mlunit.test_result;
Ferner ist es möglich, ein oder mehrere Pakete innerhalb einer Methode (und nur dort) zu importieren:
function test_foobar()
import mlunit.*;
result = test_result();
end
Etwas merkwürdig ist jedoch, dass auch innerhalb eines Paketes dessen Name referenziert werden muss. Obiges Beispiel für die Klasse "text_test_result" wird demnach zu:
classdef text_test_result < mlunit.test_result
methods
function should_stop = get_should_stop(self)
fprintf(1, 'Should stop.\n');
should_stop = get_should_stop@mlunit.test_result(self);
end
end
end
Die Referenz auf den Paketnamen innerhalb von "get_should_stop" lässt sich wiederum durch die "import"-Anweisung verhindern, während die erbende Klasse in der ersten Zeile stets den Paketnamen tragen muss (jedenfalls habe ich keine Möglichkeit gefunden, dies zu umgehen).
Damit endet dieser erste Einblick in die neue objektorientierten Programmierung mit MATLAB R2008a, der aber keinesfalls umfassend ist. Insbesondere fehlen noch die neuen "handle"-Klassen sowie die Möglichkeit, neben Eigenschaften und Methoden auch Ereignisse ("events") zu definieren. Sobald ich mir diese genauer angeschaut haben, wird es deshalb einen weiteren Artikel geben.
Auch kann man die Beispiele in diesem Artikel durchaus als Hinweis deuten, das mlUnit 2.0 in Arbeit ist... :)