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.

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.

Abbildung 1. Ein Sensorarray, das zwei entfernte elektromagnetische Quellen an unbekannten Winkeln erfasst.

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.

Abbildung 2. Klassendefinitionsdatei sads.m mit Eigenschaften.

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: GetAccessConstant und Dependent (Abbildung 3).

Abbildung 3. Klassendefinitionsdatei sads.m mit Eigenschaftsattributen. 

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

Abbildung 4. Klassendefinitionsdatei sads.m mit Methoden, im MATLAB-Editor angezeigt. Um die Anzeige zu erleichtern, wird die Code-Einklapp-Funktion verwendet, um einen Großteil des Codes auszublenden.

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 = [mags,fflip] = 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) 

Abbildung 5. Überladene, für den Sensorarray-Datensatz spezialisierte Darstellungsmethode.

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


Artikel für verwandte Branchen anzeigen