Klasse class

Was sind Klassen und Objekte

Eine Klasse ist eine Kategorie, eine Einteilung. Wir abstrahieren Dinge aus unserer Umgebung und fassen sie zu einem einheitlichen Typ zusammen. Beispielsweise abstrahieren wir alle Menschen auf der Erde zu einer Klasse „Mensch“. Einzelne konkrete Personen wie Bob oder Tom sind dann Objekte.

Eine Klasse funktioniert wie ein „Bauplan“ oder eine „Vorlage“, die Struktur und Verhalten aller zugehörigen Objekte definiert.

Nehmen wir wieder die Klasse Mensch als Beispiel: Diese Vorlage legt fest, was ein Mensch besitzt und was er tun kann. Er hat einen Namen, Arme, Beine und andere Körperorgane; er kann sprechen und nachdenken. Angaben wie Name oder Alter zählen zu den Eigenschaften einer Klasse, Handlungen wie Sprechen oder Denken zu ihrem Verhalten. Kurz gesagt extrahiert eine Klasse gemeinsame Merkmale aus realen Objekten. Mit dieser Vorlage lassen sich beliebig viele einzelne Objekte wie Jack oder Jim erstellen.

Die zuvor genannten Klasseneigenschaften (Name, Alter) und das Klassenverhalten (Sprechen, Denken) werden jeweils durch Variablen und Funktionen abgebildet.

Sie legt fest, welche Eigenschaften (Variablen) und Methoden (Funktionen) ein Objekt haben muss.

Eine Klasse lässt sich mit einer Backform vergleichen: Sie ist kein fertiges Produkt. Mit einer einzigen Form lassen sich viele einzelne Brote herstellen, genau wie mit einer Klasse viele Objekte.

Zu Beginn der Programmiergeschichte gab es noch kein Konzept der Klasse. Die Sprache C ist ein typisches Beispiel: Sie arbeitet nur mit Funktionen, Variablen und verschiedenen Anweisungen – man spricht hier von prozeduraler Programmierung. Mit wachsender Komplexität von Software zeigten sich gravierende Nachteile dieses Ansatzes: Viel doppelter Code, unübersichtliche Strukturen und fehlende Hierarchien. Später entwickelte sich das objektorientierte Paradigma: Man abstrahiert Programmlogik in einzelne Objekte, analog zur echten Welt. Man kann das mit einem Unternehmen vergleichen: Am Anfang erledigt der Chef alle Aufgaben selbst. Sobald er Mitarbeiter einstellt, delegiert er Buchhaltung an die Finanzabteilung und Personalbeschaffung an die Personalabteilung. Durch die Aufteilung der Aufgaben wird der gesamte Ablauf deutlich übersichtlicher.

Ein Objekt ist eine Instanz einer Klasse.

Wenn Sie das Schlüsselwort new oder einen Konstruktor aufrufen, erzeugt die Klasse ein neues Objekt.

Ein Objekt ist eine zur Laufzeit vorhandene Instanz, die Daten speichern und Methoden aufrufen kann.

Die Beziehung zwischen Klassen und Objekten

  • Die Klasse ist der Bauplan, das Objekt das daraus gebaute fertige Haus.
  • Aus einer einzigen Klasse lassen sich mehrere Objekte erzeugen, jedes mit eigenen unabhängigen Daten.
  • Alle Objekte aus derselben Klasse teilen die darin definierten Verhaltensweisen (Methoden).

Wechseln wir nun zur Sprache Dart

Folgende Themen werden wir behandeln

Dart ist eine vollständig objektorientierte Sprache. Jeder Wert ist eine Instanz einer Klasse. Außer Null erben alle Klassen von der Basisklasse Object.

Dart realisiert Vererbung durch die Kombination von Klassen und Mixins

Einfachvererbung: Jede Klasse kann nur eine einzige Elternklasse haben, aber Mixins ermöglichen die Wiederverwendung mehrerer Logikblöcke aus verschiedenen Klassen

Erweiterungsmethoden: Man kann einer Klasse neue Funktionen hinzufügen, ohne deren Quellcode zu ändern oder eine Unterklasse zu erstellen

Klassenmodifikatoren: Steuern, ob andere Bibliotheken diese Klasse erben oder implementieren dürfen

Jetzt beginnen wir mit dem Lernen von Klassen in Dart

Klassenmitglieder

Zu den Klassenmitgliedern zählen Methoden und Variablen – sie sind die Bestandteile, aus denen eine Klasse besteht.

Mit dem Operator . greift man auf Instanzmitglieder zu; der Null-Sicherheitsoperator ?. verhindert Nullpointer-Ausnahmen

Hier ist ein vollständiges Beispiel, das das Definieren einer Klasse, das Erstellen von Objekten aus der Klasse und das Aufrufen von Mitgliedern über den Objektnamen zeigt

// Klasse definieren, entspricht dem Erstellen einer Form
class Point {
  double x, y;
  Point(this.x, this.y);

  // Instanzmethode: Berechnet das Quadrat des Abstands zwischen zwei Punkten
  double distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return dx * dx + dy * dy;
  }
}

void main() {
  // Objekt p aus der Klasse Point erstellen, um Operationen durchzuführen
  var p = Point(2, 2);

  // Auslesen einer Instanzvariablen (impliziter Getter)
  print(p.y); //2.0

  // Aufruf einer Instanzmethode
  double dist = p.distanceTo(Point(4, 4));
  print(dist); // 8.0

  // Null-sicherer Zugriff mit ?.
  Point? nullablePoint;
  var a = nullablePoint?.y; // Ist das Objekt null, erhält a direkt null ohne Ausnahme
  print(a); // null
}Code-Sprache: JavaScript (javascript)

Konstruktoren (Constructors)

Standardkonstruktoren

Dart unterscheidet sich von anderen C-ähnlichen Sprachen durch unterstützte benannte Konstruktoren. Bei den meisten C-abgeleiteten Sprachen muss der Konstruktor den gleichen Namen wie die Klasse tragen, Dart erlaubt dagegen Konstruktoren mit von dem Klassennamen abweichenden Bezeichnern.

Syntax: Klassenname() / Klassenname.Bezeichner();

Das Schlüsselwort new ist optional (verfügbar ab Dart 2)

class Point {
  double x, y; // Zwei Variablen als Klassenmitglieder zur Speicherung von Klasseneigenschaften

  // Standardkonstruktor zur Initialisierung der Variablen x und y
  Point(this.x, this.y);

  // Benannter Konstruktor Point.fromJson: Initialisiert x und y mit JSON-Daten
  Point.fromJson(Map<String, double> json)
      : x = json['x']!,
        y = json['y']!;

}

void main() {
  var p1 = Point(2, 2);// Aufruf des Standardkonstruktors, übergibt 2 und 2, initialisiert p1 mit x=2, y=2

  var p2 = Point.fromJson({'x': 1, 'y': 2}); // Übergibt JSON an den benannten Konstruktor fromJson

  // new kann weggelassen werden, das Verhalten ist identisch
  var p3 = new Point(3, 3); // Erstellt Objekt über den Standardkonstruktor

  print("p1:(${p1.x},${p1.y}), p2:(${p2.x},${p2.y}), p3:(${p3.x},${p3.y})");
  // Ausgabe: p1:(2.0,2.0), p2:(1.0,2.0), p3:(3.0,3.0)
}Code-Sprache: JavaScript (javascript)

Konstantenkonstruktoren mit const

Stellen Sie einem Konstruktor das Schlüsselwort const voran, um Kompilierzeit-Konstantenobjekte zu erzeugen. Konstanten mit identischen Parametern nutzen dieselbe Instanz wieder.

class ImmutablePoint {
  final double x, y;
  // Regel für Konstantenkonstruktoren: Alle Mitglieder müssen mit final markiert sein
  const ImmutablePoint(this.x, this.y);
}

void main() {
  // Erstellen von Konstanteninstanzen
  var a = const ImmutablePoint(1, 1); // const ist auch bei der Instanzerstellung erforderlich
  var b = const ImmutablePoint(1, 1);
  print(identical(a, b)); // true, verweisen auf dasselbe Objekt

  var m = const ImmutablePoint(1, 1); 
  var n = const ImmutablePoint(2, 1);
  print(identical(m, n)); // false, abweichender x-Wert ergibt separate Objekte

  // Ohne const handelt es sich um normale Objekte ohne gemeinsame Instanz
  var c = ImmutablePoint(1, 1);
  print(identical(a, c)); //false

  // Innerhalb eines const-Kontexts kann das innere const weggelassen werden
  const pointMap = {
    'p1': [ImmutablePoint(0, 0)],
    'p2': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)]
  };

  print(pointMap); //{p1: [Instance of 'ImmutablePoint'], p2: [Instance of 'ImmutablePoint', Instance of 'ImmutablePoint']}

  print(identical(p1, p2)); // false
  print(identical(p2, p3)); // false
  print(identical(p1, p3)); // false
}Code-Sprache: PHP (php)
Ein Konstantenkonstruktor nutzt nur dann dieselbe Instanz wieder, wenn alle übergebenen Parameter vollständig übereinstimmen. Schon ein abweichender Parameter erzeugt zwei unabhängige Objekte.

Laufzeittyp eines Objekts abfragen mit runtimeType

Mit Objekt.runtimeType erhalten Sie den Typ zur Laufzeit. Bevorzugen Sie aber den Typ-Prüfoperator is statt Vergleiche mit runtimeType

class Point {
  double x, y;
  Point(this.x, this.y);

  // Instanzmethode: Berechnet das Quadrat des Abstands zwischen zwei Punkten
  double distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return dx * dx + dy * dy;
  }
}

void main() {
  var p = Point(2, 3);
  print("Typ von p: ${p.runtimeType}"); // Point

  // Empfohlene stabile Prüfweise
  if (p is Point) {
    print("p ist eine Instanz von Point");
  }

  // Für Produktcode nicht empfohlen
  if (p.runtimeType == Point) {
    print("p ist eine Instanz von Point ");
  }
}Code-Sprache: JavaScript (javascript)

Instanzvariablen Instance Variables

  1. Nicht initialisierte nullable Typen haben standardmäßig den Wert null; nicht-nullbare Typen müssen zwingend initialisiert werden
  2. Alle Instanzvariablen erhalten automatisch einen impliziten Getter; nicht-final oder uninitialisierte late-final-Variablen bekommen zusätzlich einen automatischen Setter
  3. Normale Initialisierungsausdrücke von Instanzvariablen dürfen this nicht aufrufen; bei late-Variablen ist der Zugriff auf this in der Initialisierung erlaubt
  4. Final-Instanzvariablen lassen sich nur einmal zuweisen: Bei der Deklaration, über Konstruktorparameter oder in der Konstruktor-Initialisierungsliste

Implizite Getter und Setter

class Point {
  double? x; // Initialwert null, automatischer Getter + Setter
  double? y;
  double z = 0; // Initialwert 0
}

void main() {
  var point = Point();
  point.x = 4; // Aufruf des impliziten Setters
  assert(point.x == 4); // Aufruf des impliziten Getters
  assert(point.y == null);
  print(point.z); // 0
}
Code-Sprache: JavaScript (javascript)

Zugriffsbeschränkung von this in Initialisierungsausdrücken

double globalX = 1.5;
class Point {
  // Erlaubt: Zugriff auf externe globale Variable ohne this
  double? x = globalX;

  // Kompilierfehler: Standardinitialisierung darf this nicht nutzen
  // double? y = this.x;

  // Erlaubt: late-Variablen dürfen this bei der Initialisierung referenzieren
  late double? z = this.x;

  // Erlaubt: Der Syntax-Sugar this.x bei Konstruktorparametern zählt nicht als Initialisierungsausdruck
  Point(this.x, this.y);
  double? y;
}

void main() {
  var p = Point(null, 5);
  print(p.z); // null
}
Code-Sprache: JavaScript (javascript)

Drei Initialisierungswege für final-Instanzvariablen

class ProfileMark {
  // Weg 1: Direkte Initialisierung bei der Deklaration
  final DateTime start = DateTime.now();
  final String name;

  // Weg 2: Zuweisung über Konstruktorparameter
  ProfileMark(this.name);

  // Weg 3: Zuweisung in der Konstruktor-Initialisierungsliste; der benannte Konstruktor unnamed setzt name standardmäßig auf leeren String
  ProfileMark.unnamed() : name = '';
}

void main() {
  var m1 = ProfileMark("user1");
  var m2 = ProfileMark.unnamed();
  print(m1.name);
  print(m2.name);
}
Code-Sprache: PHP (php)

Implizite Schnittstellen Implicit interfaces

Jede Dart-Klasse besitzt eine implizite Schnittstelle, die alle öffentlichen Instanzmitglieder der Klasse umfasst.

  • implements: Implementiert eine Schnittstelle, alle Methoden der Schnittstelle müssen überschrieben werden
  • extends: Vererbung, wiederverwendet die Implementierung der Elternklasse
  • Private Variablen mit Präfix _xxx werden nicht an die Schnittstelle exponiert

1 Implementieren einer einzelnen Schnittstelle

class Person {
  final String _name;
  Person(this._name);
  // Öffentliche Schnittstellenmethode
  String greet(String who) =&gt; "Hello $who, I'm $_name";
}

// Implementiert die Person-Schnittstelle, alle Schnittstellenmitglieder müssen überschrieben werden
class Impostor implements Person {
  String get _name =&gt; "";
  @override
  String greet(String who) =&gt; "Hi $who, guess who?";
}

String greetBob(Person p) =&gt; p.greet("Bob");

void main() {
  print(greetBob(Person("Kathy")));
  print(greetBob(Impostor()));
}
Code-Sprache: PHP (php)

Wenn eine Unterklasse Methoden der Elternschnittstelle überschreibt, markieren Sie dies mit der Annotation @override – ähnlich wie bei Java. Zum Implementieren einer Schnittstelle nutzen Sie das Schlüsselwort implements.

2 Implementieren mehrerer Schnittstellen

// Abstrakte Klasse
abstract class Comparable {
  int compareTo(Object other);
}

// Abstrakte Klasse
abstract class Location {
  double get x;
  double get y;
}

// Implementiert zwei Schnittstellen gleichzeitig
class Point implements Comparable, Location {

  @override
  double x, y;

  Point(this.x, this.y);

  @override
  int compareTo(Object other) {
    if (other is Point) {
      return (x + y).compareTo(other.x + other.y);
    }
    return -1;
  }
}

void main() {
  var p = Point(2, 3);
  print(p.x);
  print(p.compareTo(Point(1, 1)));
}
Code-Sprache: PHP (php)

@override
double x, y;

Diese Annotation bedeutet: Diese beiden Eigenschaften überschreiben die abstrakten Getter, die in der übergeordneten Schnittstelle Location definiert sind.

Statische Variablen & statische Methoden static

1. Statische Variablen static (Klassenvariablen, global eindeutig)

  • Sie gehören zur Klasse selbst, nicht zu einzelnen Instanzen; verzögerte Initialisierung: Sie werden erst bei dem ersten Zugriff erstellt
  • Konstante statische Variablen sollten nach lowerCamelCase benannt werden
class Queue {
  static const initialCapacity = 16; // Statische Konstante
  static int count = 0; // Statische veränderliche Variable
  Queue() {
    count++;
  }
}

void main() {
  assert(Queue.initialCapacity == 16); // Wird bei Zugriff initialisiert, Standardwert 16
  Queue(); // Erhöht count um 1
  Queue();
  print(Queue.count); // 2
}
Code-Sprache: JavaScript (javascript)

2. Statische Methoden static (Klassenmethoden)

  • Sie besitzen keine Referenz this, können also keine Instanzmitglieder aufrufen, nur statische Variablen
  • Aufruf direkt über die Syntax Klassenname.Methode()
  • Sie können als Kompilierzeitkonstanten an Konstantenkonstruktoren übergeben werden
import 'dart:math';

class Point {
  double x, y;
  Point(this.x, this.y);

  // Statische Methode: Berechnet den direkten Abstand zwischen zwei Punkten
  static double distanceBetween(Point a, Point b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
  }
}

void main() {
  var a = Point(2, 2);
  var b = Point(4, 4);
  var dist = Point.distanceBetween(a, b);
  print(dist); // ca. 2,828
}Code-Sprache: JavaScript (javascript)

Statische Methoden gehören zur Klasse, daher müssen sie über den Klassennamen aufgerufen werden, wobei zwei Point-Instanzen als Parameter übergeben werden.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert