Einführung in die Objekt-Orientierte Programmierung mit MATLAB
Von Stuart McGarrity und Adam Sifounakis, MathWorks
Bei der Erstellung von Software-Anwendungen ist es wichtig, die verschiedenen Bausteine Ihrer Software in verwandte Gruppen einzuordnen. Beispielsweise kann ein benutzerdefinierter numerischer Solver mehrere Konfigurationsparameter und Routinen erfordern, um einen vollständigen Satz an Berechnungen durchzuführen. Die objektorientierte Programmierung (OOP) ermöglicht es Ihnen, die Konfigurationsparameter (Eigenschaften) des Solvers mit seinen Funktionen (Methoden) in einer einzigen Definition oder Klasse zu gruppieren. Alles, was ein Benutzer benötigt, um diesen Solver korrekt auszuführen, wird in dieser Klasse definiert.
Ein Objekt ist eine Instanz einer Klasse. Wenn ein Programm ausgeführt wird, wird das Objekt auf Grundlage seiner Klassendefinition erstellt, und es verhält sich so, wie es von der Klasse definiert wurde. Die Eigenschaften eines Objekts stellen seinen Zustand dar und seine Methoden repräsentieren sämtliche Aktionen, die ein Benutzer ausführen kann. Auf diese Weise kann ein Code-Autor alle zugehörigen Daten und Funktionen für ein Softwaresystem einfach gruppieren, und ein Benutzer kann alle Funktionalitäten, die der Code-Autor entwickelt hat, leicht finden und nutzen.
Das folgende Beispiel nutzt objektorientierte Programmierung, um eine Anwendung zu erstellen, die Sensordaten aus einem Sensor-Array analysiert.
Der in diesem Artikel verwendete Code steht Ihnen als Download zur Verfügung.
Anwendungsbeispiel: Analysieren von Sensorarray-Daten
Ein Sensorarray (Abbildung 1) ist eine Sammlung von Sensoren, die oft in einer Reihe angeordnet sind und zur Datenerfassung aus einem Medium wie Luft, Wasser oder Boden für Radar, Sonar oder für Mobilfunk verwendet werden. Durch das Sammeln von Daten von mehreren Punkten im Raum aus kann man zusätzliche Informationen aus dem entsprechenden Medium extrahieren.
Unsere Anwendung verwendet ein Sensorarray zur Bestimmung der Ankunftsrichtung (AR) mehrerer entfernter elektromagnetischer Quellen, wie z. B. Funkfeuer und Radarsender. In diesem Szenario werden wir versuchen, die Winkel θ1 und θ2 der beiden Quellen relativ zur Richtung, in die das Sensorarray zeigt, abzuschätzen.
Untersuchung der zur Verfügung stehenden Daten und Operationen
Für diese Anwendung müssen wir die folgenden Daten speichern und darstellen:
- Anzahl der Sensoren und Datenpunkte
- Abgetastete Sensordaten
- Sensor-Abtastrate
- Abstand zwischen den Sensoren
- Wellenlänge entfernter Quellen
- Geschwindigkeit der Welle
- Name oder Beschreibung des Sensordatensatzes
Wir werden eine einfache Technik auf Basis der schnellen Fourier-Transformation (FFT) verwenden, um die AR der Quellen zu schätzen. Diese Technik kann in Teilschritte zerlegt und als eine Sammlung von Operationen umgesetzt werden. Zur Vereinfachung der Entwicklungsarbeit wird eine kleine Anzahl von Hilfsfunktionen implementiert. So müssen wir z. B. Folgendes tun:
- Den Datensatz aus synthetischen Daten oder erfassten Live-Daten erstellen
- Datensatzwerte und -parameter prüfen und ändern
- Den Datensatz zur Hilfe bei der Interpretation und Validierung grafisch darstellen
- Das Leistungsdichtespektrum des Datensatzes berechnen und aufzeichnen (durch einfaches Betragsquadrat der FFT-Methode)
- Die Spitzenwerte im Leistungsdichtespektrums finden, um die Ankunftsrichtung der Quellen abzuschätzen
Nachdem wir die Daten, die wir repräsentieren müssen, und die Aktivitäten, die wir durchführen müssen, identifiziert haben, können wir die Daten mit Klasseneigenschaften und die Aktivitäten mit Klassenmethoden repräsentieren.
Darstellung von Daten mit Klasseneigenschaften
Wir beginnen mit der Definition einer Klasse zur Beschreibung des Sensorarrays. Diese anfängliche Darstellung enthält nur die Datenelemente und stellt sie als Klasseneigenschaften dar.
Mit einer Klassendefinitionsdatei definieren Sie eine Klasse in MATLAB®, die mit dem Schlüsselwort classdef
beginnt und mit dem Schlüsselwort end
abgeschlossen wird. Innerhalb des Klassendefinitionsblocks beschreiben zusätzliche Schlüsselwortblöcke verschiedene Aspekte der Klasse, wie z. B. Klasseneigenschaften und Klassenmethoden. Die in Abbildung 2 gezeigte Definitionsdatei beschreibt eine Klasse sads
(Sensorarray-Datensatz) mit allen Datenelementen, die wir darstellen müssen, aufgelistet in einem Eigenschaftsblock.
Erstellen eines Objekts und Zugriff auf Eigenschaften
Um ein Objekt oder eine Instanz der von uns definierten Klasse zu erzeugen, verwenden wir die Anweisung
>> s = sads;
Um den Wert einer Eigenschaft festzulegen, benennen wir ihren Namen genau wie Felder einer Struktur mit
>> s.NumSensors = 16;
Wir können das Objekt anzeigen und alle verfügbaren Eigenschaften und aktuellen Werte sehen, indem wir seinen Namen eingeben.
>> s s = sads with properties: Wavelength: [] c: 300000000 NumSensors: 16 NumSamples: [] Data: [] Spacing: [] SampleRate: [] Name: []
Alle Eigenschaften außer NumSensors
und c
sind noch leer. Der Datensatz kann nun mit Hilfe der class
-Funktion, der isa
-Funktion und des whos
-Befehls als SADS
-Objekt identifiziert werden, was bei Strukturen nicht möglich ist.
>> class(s) ans = 'sads'
Die Fähigkeit, die Klasse einer Variablen zu identifizieren, ist wichtig für Benutzer, die Code zum Bearbeiten des Datensatzes erstellen, da sie dadurch die verfügbaren Datenelemente, auf die zugegriffen werden kann, sowie zulässige Operationen bestimmen können.
Fehlerüberprüfung
Wenn Sie Strukturen zur Darstellung Ihrer Daten verwenden, können Sie jederzeit ein neues Feld hinzufügen, indem Sie einfach einen neuen Feldnamen angeben und ihm einen Wert zuweisen. Diese Möglichkeit ist besonders praktisch, wenn Sie mit Algorithmen experimentieren und Prototypen erstellen. Wenn Sie jedoch einen Feldnamen falsch schreiben, wird stillschweigend ein neues Feld hinzugefügt, was später zu einem schwer zu diagnostizierenden Fehler führen kann.
Im Gegensatz zu Strukturen können Sie einem Objekt nicht dynamisch eine neue Eigenschaft hinzufügen, indem Sie einfach einen neuen Eigenschaftsnamen angeben und ihm einen Wert zuweisen. Wenn Sie den Namen einer Objekteigenschaft falsch schreiben, gibt MATLAB sofort eine Fehlermeldung aus. Diese zusätzliche Ebene der Fehlerprüfung ist nützlich, wenn auf das Objekt von Benutzern zugegriffen wird, die mit dem Objekt weniger vertraut sind als der Autor, was bei der Entwicklung einer großen Anwendung häufig der Fall ist.
Zugriff auf Daten kontrollieren
Klassen bieten Ihnen eine große Kontrolle über den Zugriff auf Eigenschaften. So können Sie beispielsweise die Änderung einer Eigenschaft verbieten, eine Eigenschaft ausblenden oder ihre dynamische Berechnung veranlassen. Sie steuern den Zugriff auf Eigenschaften durch die Angabe von Eigenschaftsattributen in der Klassendefinitionsdatei. Wir erweitern die Klassendefinitionsdatei in Abbildung 2, indem wir die aktuelle Liste der Eigenschaften in mehrere Eigenschaftsblöcke mit jeweils eindeutigen Eigenschaftsattributen unterteilen: GetAccess
, Constant
und Dependent
(Abbildung 3).
Sie unterbinden die Änderung einer Eigenschaft durch Festlegen des Attributs Constant
. In unserem Beispiel setzen wir die Eigenschaft Lichtgeschwindigkeit c
als konstant fest. Da sich konstante Eigenschaften nicht ändern, kann auf sie einfach durch Verweis auf den Klassennamen zugegriffen werden.
>> sads.c ans = 300000000
Sie machen eine Eigenschaft schreibgeschützt, indem Sie das SetAccess
-Attribut auf private
setzen. Sie können eine Eigenschaft nur für die Methoden sichtbar machen, die mit ihr arbeiten, indem Sie das GetAccess
-Attribut auf private
setzen, so wie wir es mit der Eigenschaft Wavelength
tun werden.
Sie können die Namen oder Merkmale einer mit dem Schlüsselwort private
markierten Eigenschaft beliebig ändern, ohne dass dies die Benutzer des Objekts betrifft. Dieser „Black-Box“-Ansatz zur Definition einer Software, der als Kapselung bekannt ist, verhindert, dass der Benutzer des Objekts von einem Implementierungsdetail oder einer Eigenschaft abhängig wird, die eine Änderung in seinem Code erfordern oder seinen Code sogar nicht ausführbar machen könnte.
Man kann festlegen, dass eine Eigenschaft nur berechnet wird, wenn sie angefordert wird, indem Sie das Attribut Dependent
setzen.
Sie geben dann eine Get-Methode an, die beim Zugriff auf die Eigenschaft automatisch aufgerufen wird. Einzelheiten zum Definieren von Klassenmethoden finden Sie im Abschnitt „Zugriff auf Eigenschaften mit Get- und Set-Methoden“ in diesem Artikel. In unserer Anwendung legen wir die Eigenschaften NumSensors
und NumSamples
als Dependent
fest.
Implementieren von Operationen mit Klassenmethoden
Methoden bzw. die Operationen, die auf dem Objekt ausgeführt werden können, werden als eine Liste von Funktionen in einem Methodenblock angegeben. Eine Klasse kann viele Arten von Methoden enthalten, die jeweils einen anderen Zweck erfüllen und unterschiedlich spezifiziert sind. Der folgende Abschnitt beschreibt eine Reihe dieser Methodentypen.
Wir werden der SADS
-Definitionsdatei den Block methods hinzufügen und jede neue Methode in diesen Block einfügen (Abbildung 4).
Angeben einer Konstruktormethode
In unserem Beispiel spezifizieren wir eine Konstruktormethode, mit der der Benutzer Parameter angeben kann, die bei der Erstellung des Objekts verwendet werden sollen. Die Konstruktormethode führt häufig eine Dateninitialisierung und -validierung durch. Das Objekt wird nun erstellt mit dem Befehl
>> s = sads(Data, Wavelength, SampleRate, Spacing, Name);
Implementieren anwendungsspezifischer Methoden
Wir werden mehrere Methoden zur Implementierung anwendungsspezifischer Operationen hinzufügen, die auf dem Datensatz durchgeführt werden sollen. Die meisten Methoden nutzen das Objekt als Eingabeargument (z. B. obj
) und greifen auf die Objekteigenschaften zu, indem sie auf diese Variable verweisen (z. B. obj.NumSamples
) wie in folgender Methode:
function mag = magfft(obj, zpt) mag = zeros(obj.NumSamples, zpt); ... end
Obwohl es zusätzliche Syntax erfordert, kann das Referenzieren von Eigenschaften über die Objektvariable helfen, sie von lokalen Funktionsvariablen, wie z. B. mag
oben, zu unterscheiden.
Aufruf von Methoden
Methoden werden genau wie Funktionen aufgerufen, wobei das übergebene Objekt eines der Argumente ist. Wir können die AR-Winkel der Quellen abschätzen, indem wir die doa
-Methode unserer Klasse aufrufen.
>> angles = doa(s) angles = -10.1642 18.9953
Die AR-Winkel sind Näherungen der wahren Positionen der in Abbildung 1 gezeigten Quellen, die sich bei -10° und 20° befinden.
Zugriff auf Eigenschaften mit Get- und Set-Methoden
Sie können Eigenschaften validieren oder Dependent-Eigenschaften implementieren, indem Sie zugehörige Set- und Get-Methoden angeben. Hier ist die Get-Methode für die NumSensors
-Eigenschaft.
function NumSensors = get.NumSensors(obj) NumSensors = size(obj.Data, 2); end
Get- und Set-Methoden werden automatisch aufgerufen, wenn auf Eigenschaften zugegriffen wird, zum Beispiel mit
>> N = s.NumSensors;
Spezifizieren von Methoden für vorhandene MATLAB-Funktionen mittels Überladen
Durch Überladen können Sie vorhandene MATLAB-Funktionen für die Verwendung mit Ihrem Objekt neu definieren, indem Sie eine Funktion mit demselben Namen in Ihrer Methodenliste bereitstellen. In unserer Anwendung werden wir die Methode plot
überladen, die eine Funktion zur Visualisierung des Datensatzes bereitstellt, die vielen MATLAB-Anwendern vertraut ist (Abbildung 5).
>> plot(s)
Diese benutzerdefinierte plot
-Methode stellt die Informationen auf die für diesen Datensatz am besten geeignete Weise dar, wobei die Achsen mit den entsprechenden Beschriftungen versehen werden. Sie wird nur bei Objekten ausgeführt, für die sie definiert wurde – ein viel robusterer Ansatz als die Manipulation der Reihenfolge von Verzeichnissen im Pfad.
Wenn Sie spezielles Verhalten für Ihre Klasse wünschen, können Sie auch grundlegende Operatoren und sogar die Indexierung überladen, indem Sie Methoden mit speziellen Namen verwenden.
Die Anwendung weiterentwickeln
Die Klasse, die wir in diesem Beispiel erstellt haben, stellt unseren Sensorarray-Datensatz dar und ermöglicht es uns, auf einfache Weise eine komplexe, spezialisierte Analyse unserer Daten durchzuführen, einschließlich des Herzstücks, der Bestimmung der Ankunftsrichtung. Mit dieser Klasse können wir die Genauigkeit der FFT-basierten Technik in verschiedenen Szenarien schnell bewerten.
Wir könnten die Anwendung durch zusätzliche OO-Techniken erweitern. Wir könnten zum Beispiel Folgendes tun:
- Definieren von Unterklassen bestehender Klassen (Wiederverwendung einer Definition einer breiteren Kategorie zur Definition einer spezifischeren Unterkategorie) über Vererbung
- Angabe von statischen Methoden, um eine Operation für die Klasse als Ganzes zu definieren
- Verwendung von Handle-Klassen mit Referenzverhalten, das es uns ermöglicht, Datenstrukturen wie etwa verknüpfte Listen zu erstellen oder mit einem großen Datensatz zu arbeiten, ohne ihn zu kopieren
- Definieren von Events und Listeners, um Objekteigenschaften oder Aktionen zu überwachen
Diese Techniken verbessern unsere Fähigkeit, mit Komplexität umzugehen, indem sie es uns ermöglichen, Beziehungen und Verhalten in der Anwendung weiter zu definieren.
Da sie mit OO-Techniken erstellt wurde, ist die Anwendung jetzt robust genug, um auch von anderen genutzt und gewartet zu werden, und sie kann mit anderen Anwendungen innerhalb einer Organisation integriert werden.
Veröffentlicht 2020