Dart-Klassen und Objekte

Eine Klasse ist ein Kernkonzept der objektorientierten Programmierung; sie ist eine abstrakte Beschreibung von Dingen in der realen Welt.

Objekte sind konkrete Instanzen von Klassen. Dieses Kapitel führt in Klassendefinitionen, Konstruktoren, Membervariablen und -methoden, Getter/Setter und statische Member in Dart ein.


Klassendefinition und Instanziierung

Eine Klasse ist eine Vorlage zum Erstellen von Objekten und definiert, welche Eigenschaften (Daten) und Verhaltensweisen (Methoden) die Objekte haben.

Beispiel

// Definiere eine User-Klasse
class User {
  // Membervariablen (Eigenschaften)
  String name = '';
  int age = 0;

  // Membermethoden (Verhalten)
  void introduce() {
    print('Hallo, ich bin $name, ich bin $age Jahre alt.');
  }

  bool isAdult() {
    return age >= 18;
  }
}

void main() {
  // Instanziieren: mit dem new-Schlüsselwort (new kann in Dart 2.0+ weggelassen werden)
  var user1 = User();
  user1.name = 'example';
  user1.age = 10;

  // Zugriff auf Membervariablen und Methoden
  print('Benutzername: ${user1.name}');
  user1.introduce();
  print('Ist erwachsen: ${user1.isAdult()}');

  // Ein weiteres Objekt erstellen, unabhängig voneinander
  var user2 = User();
  user2.name = 'Xiao Ming';
  user2.age = 20;
  user2.introduce();
}
用户名: example
你好,我是 example,今年 10 岁。
是否成年: false
你好,我是 小明,今年 20 岁。

Seit Dart 2.0 ist das Schlüsselwort `new` beim Instanziieren optional. Die Konvention in der Community ist, `new` wegzulassen, um den Code knapp zu halten. Nachfolgende Beispiele in diesem Tutorial werden `new` weglassen.


Konstruktor

Ein Konstruktor ist eine spezielle Methode, die automatisch aufgerufen wird, wenn ein Objekt erstellt wird, und dazu dient, den Zustand des Objekts zu initialisieren.

Dart bietet mehrere Formen von Konstruktoren, was sie sehr flexibel macht.

Standardkonstruktor

Wenn Sie keinen Konstruktor definieren, stellt Dart automatisch einen argumentlosen Standardkonstruktor bereit.

Regulärer Konstruktor

Beispiel

class Student {
  String name;
  int age;

  // Normaler Konstruktor: gleicher Name wie die Klasse
  Student(String name, int age) {
    this.name = name;
    this.age = age;
  }

  void printInfo() {
    print('Student: $name, Age: $age');
  }
}

void main() {
  var s1 = Student('example', 10);
  var s2 = Student('Xiao Ming', 15);
  s1.printInfo();
  s2.printInfo();
}
学生: example, 年龄: 10
学生: 小明, 年龄: 15

Konstruktor mit syntaktischem Zucker

Wenn Konstruktorparameter direkt Membervariablen zugewiesen werden, bietet Dart eine Kurzsyntax.

Beispiel

class Student {
  String name;
  int age;

  // Syntaktischer Zucker: this.parameterName weist den Parameter automatisch einer Membervariable mit demselben Namen zu
  // Diese Schreibweise ist prägnanter als das manuelle Schreiben von this.name = name
  Student(this.name, this.age);

  void printInfo() {
    print('EXAMPLE Student: $name, Age: $age');
  }
}

void main() {
  var s = Student('example', 10);
  s.printInfo();
}
EXAMPLE 学生: example, 年龄: 10

Benannter Konstruktor

Eine Klasse kann mehrere benannte Konstruktoren für unterschiedliche Initialisierungslogiken haben.

Beispiel

class Point {
  double x;
  double y;

  // Standardkonstruktor
  Point(this.x, this.y);

  // Benannter Konstruktor: erzeugt den Ursprung
  Point.origin()
      : x = 0,
        y = 0;

  // Benannter Konstruktor: erzeugt aus einem einzelnen Wert (x und y sind gleich)
  Point.diagonal(double value)
      : x = value,
        y = value;

  @override
  String toString() => 'Point($x, $y)';
}

void main() {
  var p1 = Point(3, 4);
  var p2 = Point.origin();
  var p3 = Point.diagonal(5);

  print('BEISPIEL Koordinaten: $p1, $p2, $p3');
}
EXAMPLE 坐标: Point(3.0, 4.0), Point(0.0, 0.0), Point(5.0, 5.0)

Initialisierungsliste

Die Initialisierungsliste wird ausgeführt, bevor der Konstruktorkörper ausgeführt wird, und wird verwendet, um final-Felder zu initialisieren oder eine Parametervalidierung durchzuführen.

Beispiel

class Person {
  final String name;    // final-Felder müssen im Konstruktor initialisiert werden
  final int birthYear;
  final int age;

  // Initialisierungsliste: nach dem Doppelpunkt, vor dem Funktionskörper
  // Die Initialisierungsliste wird ausgeführt, bevor der Konstruktorkörper ausgeführt wird
  Person(this.name, this.birthYear)
      : age = 2026 - birthYear,          // Berechnete Eigenschaft
        assert(birthYear > 1900, 'Ungültiges Geburtsjahr');  // Assertion-Prüfung

  void printInfo() {
    print('BEISPIEL Benutzer: $name, Geburt: $birthYear, Alter: $age');
  }
}

void main() {
  var p = Person('beispiel', 2016);
  p.printInfo();

  // Die folgende Zeile löst einen Assert-Fehler aus (im Entwicklungsmodus)
  // var p = Person('error', 1800);
}
EXAMPLE 用户: example, 出生: 2016, 年龄: 10

`assert` funktioniert nur im Debug-Modus und wird im Release-Modus ignoriert. Es wird verwendet, um Logikfehler früh in der Entwicklung zu erkennen.


Mitgliedsvariablen und Methoden

Mitgliedsvariablen speichern den Zustand eines Objekts, und Methoden definieren das Verhalten eines Objekts.

Beispiel

class BankAccount {
  // Privates Mitglied: beginnt mit einem Unterstrich, nur in dieser Datei zugänglich
  String;
  double _balance = 0;

  // Öffentliches Mitglied
  String ownerName;

  BankAccount(this.ownerName, this._accountNumber);

  // Öffentliche Methode
  void deposit(double amount) {
    if (amount > 0) {
      _balance += amount;
      print('Eingezahlt ¥$amount, aktueller Kontostand: ¥$_balance');
    }
  }

  bool withdraw(double amount) {
    if (amount > 0 && amount <= _balance) {
      _balance -= amount;
      print('Abgehoben ¥$amount, aktueller Kontostand: ¥$_balance');
      return true;
    }
    print('Nicht ausreichendes Guthaben, Abhebung fehlgeschlagen');
    return false;
  }

  // Private Methode: nur für die interne Verwendung innerhalb der Klasse
  void _logTransaction(String type, double amount) {
    print('[Internes Log] $type: ¥$amount');
  }
}

void main() {
  var account = BankAccount('EXAMPLE', '6222-0000-1234');

  account.deposit(1000);
  account.withdraw(300);
  account.withdraw(800);  // Unzureichendes Guthaben

  // Externer Code kann nicht auf private Member zugreifen
  // print(account._balance); // Fehler: _balance ist privat
}
存入 ¥1000,当前余额: ¥1000
取出 ¥300,当前余额: ¥700
余额不足,取款失败

Getter und Setter

Getter und Setter sind spezielle Methoden zum Lesen und Schreiben von Membervariablen.

Sie ermöglichen es Ihnen, beim Zugriff auf Eigenschaften zusätzliche Logik auszuführen und dabei die prägnante Syntax des Eigenschaftszugriffs beizubehalten.

Beispiel

class Circle {
  double _radius;  // Private Variable, die den Radius speichert

  Circle(this._radius);

  // Getter: gibt den Durchmesser zurück (berechnete Eigenschaft)
  double get diameter => _radius * 2;

  // Getter: gibt die Fläche zurück
  double get area => 3.14159 * _radius * _radius;

  // Getter und Setter arbeiten zusammen: machen die Radius-Eigenschaft nach außen verfügbar
  double get radius => _radius;

  set radius(double value) {
    // Validierungslogik kann im Setter hinzugefügt werden
    if (value <= 0) {
      throw ArgumentError('Radius muss größer als 0 sein');
    }
    _radius = value;
  }

  // Schreibgeschützter Getter: kein entsprechender Setter
  String get info => 'Circle(radius: $_radius)';
}

void main() {
  var circle = Circle(5);

  // Getter verwenden (wie der Zugriff auf eine normale Eigenschaft)
  print('BEISPIEL Circle: ${circle.info}');
  print('Durchmesser: ${circle.diameter}');
  print('Fläche: ${circle.area.toStringAsFixed(2)}');

  // Setter verwenden
  circle.radius = 10;
  print('Geänderter Durchmesser: ${circle.diameter}');

  // Das Setzen eines ungültigen Werts löst eine Ausnahme aus
  // circle.radius = -1; // wirft ArgumentError
}
EXAMPLE 圆形: 圆(半径: 5.0)
直径: 10.0
面积: 78.54
修改后直径: 20.0

Der Vorteil von Gettern und Settern besteht darin, dass der Aufrufer nicht wissen muss, ob `radius` ein einfaches Feld oder eine berechnete Eigenschaft ist. Wenn künftig Validierungslogik für `radius` hinzugefügt werden muss, können Sie das Feld einfach in einen Getter/Setter ändern, und der aufrufende Code muss nicht angepasst werden. Das ist der Vorteil der Kapselung.


Statische Member

Statische Member gehören zur Klasse selbst, nicht zu einer Instanz der Klasse.

Alle Instanzen teilen dieselbe statische Variable, und statische Methoden können aufgerufen werden, ohne ein Objekt zu erstellen.

Beispiel

class Counter {
  // Statische Variable: von allen Instanzen gemeinsam genutzt
  static int totalCount = 0;

  // Instanzvariable: für jede Instanz unabhängig
  String label;

  Counter(this.label) {
    // Jedes Mal, wenn eine Instanz erstellt wird, wird der statische Zähler um 1 erhöht
    totalCount++;
  }

  // Statische Methode: wird über den Klassennamen aufgerufen
  static void printTotal() {
    print('BEISPIEL Gesamtzahl der Instanzen: $totalCount');
  }

  // Statische Methoden werden oft für Hilfsfunktionen verwendet
  static bool isValidLabel(String label) {
    return label.isNotEmpty && label.length <= 20;
  }
}

void main() {
  // Statische Methode: wird direkt über den Klassennamen aufgerufen, keine Erstellung einer Instanz erforderlich
  print('Ist Label gültig: ${Counter.isValidLabel('test')}');

  var c1 = Counter('Counter A');
  Counter.printTotal();  // 1

  var c2 = Counter('Counter B');
  var c3 = Counter('Counter C');
  Counter.printTotal();  // 3

  // Statische Variable: Zugriff über den Klassennamen
  print('Final count: ${Counter.totalCount}');
}
标签是否有效: true
EXAMPLE 总实例数: 1
EXAMPLE 总实例数: 3
最终计数: 3

Statische Methoden können nicht auf Instanzmitglieder zugreifen (da es kein `this` gibt). Statische Methoden eignen sich für Hilfsfunktionen und Factory-Logik, aber verwenden Sie sie nicht zu häufig – statische Methoden können nicht von Unterklassen überschrieben werden, und eine übermäßige Verwendung verringert die Testbarkeit und Flexibilität des Codes.

Weitere Erweiterungen