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
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
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
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
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
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
// 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
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
// 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
Weitere ErweiterungenStatische 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.