Dart:面向对象(OOP)

对象和类

类(Class)的定义

类是对象的模板,定义了对象的属性(数据)和方法(行为)。

在 Dart 中,用 class 关键字定义类。

class Person {
  String name;   // 属性
  int age;       // 属性

  void sayHello() {  // 方法
    print('Hello, my name is $name');
  }
}

对象(Object)的创建

对象是类的实例

Dart 2 之后,创建对象时可以省略 new 关键字(推荐省略)。

void main() {
  var p1 = Person(); // 省略 new
  var p2 = new Person(); // 旧写法,功能一样
}

构造函数(Constructor)

构造函数在创建对象时被调用,用来初始化对象的状态。

默认构造函数

如果你没写构造函数,Dart 会自动生成一个无参构造函数。

class Person {
  // Null Safety 之后:非空类型字段必须初始化。
  // Dart 会帮你生成无参构造函数,但前提是所有非空字段都有默认值或在构造函数里赋值。
  // 如果没有提供初始值,必须在构造函数中初始化。
  String name = '';
  int age = 0;
}

void main() {
  var p = Person(); // 自动调用默认构造函数
  print(p.name); // 输出: ''
  print(p.age); // 输出: 0
}

自定义构造函数

最常用的写法是直接用参数初始化属性。

class Person {
  String name;
  int age;

  Person(this.name, this.age); // 简化写法
}

void main() {
  var p = Person('Alice', 25);
  print('Name: ${p.name}, Age: ${p.age}'); // 输出: Name: Alice, Age: 25
}

初始化列表

是什么

在 Dart 里,初始化列表(initializer list)就是构造函数在执行函数体 { ... } 之前,用来给实例变量赋值的一个特殊语法。
它用一个冒号 : 开头,写在构造函数参数列表后面。

为什么有初始化列表

Dart 对象的创建过程分两步:

  1. 初始化阶段
    • Dart 会先调用初始化列表,把字段(实例变量)赋上值。
    • 这个阶段必须保证 非空字段(non-nullable fields) 已经有值。
  2. 构造函数体执行阶段
    • 初始化完成后,才会执行 { ... } 里的代码。

因为 Dart 的 null safety 要求非空字段在初始化阶段必须赋值,所以初始化列表是非常重要的工具。

案例

class Point {
  int x;
  int y;

  // 初始化列表
  Point(int a, int b)
    : x = a, // 在构造函数体执行前赋值
      y = b {
    print('构造函数执行前:a = $a, b = $b'); // 输出: a = 3, b = 4
    x = x + 1;
    y = y + 1;
    print('构造函数体执行后:x = $x, y = $y'); // 输出: x = 4, y = 5
  }
}

void main() {
  var p = Point(3, 4);
  print('${p.x}, ${p.y}'); // 输出: 4, 5
}

执行顺序:

  1. 调用 Point(3, 4)

  2. 初始化列表先执行 → x = 3, y = 4

  3. 然后执行构造函数体

    {
      print('构造函数执行前:a = $a, b = $b'); // 输出: a = 3, b = 4
      x = x + 1;
      y = y + 1;
      print('构造函数体执行后:x = $x, y = $y'); // 输出: x = 4, y = 5
    }
    

命名构造函数

可以定义多个不同名字的构造函数。

class Point {
  num x, y;
  Map origin1, origin2;

  Point.fromJson(Map json)
    : x = json['x'],
      y = json['y'],
      origin1 = {'x': json['x'], 'y': json['y']},
      origin2 = {'x': json['x'] + 10, 'y': json['y'] + 10};
}

void main(List<String> args) {
  var p = Point.fromJson({"x": 1, "y": 2});
  print('x: ${p.x}, y: ${p.y}'); // 输出: x: 1, y: 2
  print('origin1: ${p.origin1}'); // 输出: origin1: {x: 1, y: 2}
  print('origin2: ${p.origin2}'); // 输出: origin2: {x: 11, y: 12}
}

重定向构造函数(Redirecting Constructor)

当一个构造函数不自己执行初始化逻辑,而是把工作交给类中的另一个构造函数时,就用重定向构造函数。
这样可以避免重复代码,尤其是在多个构造函数里初始化逻辑一样的情况下。

class Person {
  String name;
  int age;

  // 主构造函数
  Person(this.name, this.age);

  // 重定向构造函数:交给 Person(this.name, this.age) 处理
  Person.guest() : this('Guest', 0);
}

void main() {
  var p1 = Person('Alice', 25);
  var p2 = Person.guest();

  print('${p1.name}, ${p1.age}'); // Alice, 25
  print('${p2.name}, ${p2.age}'); // Guest, 0
}

特点

  • 重定向构造函数没有函数体(没有 {}
  • 必须直接调用 this(...)this.namedConstructor(...)
  • 只能调用本类的其他构造函数,不能调用父类的构造函数(父类用 super

Callable 类(可调用对象)

在 Dart 里,如果一个类实现了 call() 方法,那么它的实例可以像函数一样被调用,这种类叫 callable class(可调用类)。

class Adder {
  int call(int a, int b) => a + b;
}

void main() {
  var add = Adder();
  
  print(add(3, 4)); // 7,像函数一样调用
}

特点

  • call() 方法可以有任意参数和返回值
  • 调用时不需要写 add.call(3, 4),直接 add(3, 4) 就行
  • 常用于:
    • 封装函数逻辑
    • Flutter 中 widget builder
    • 高阶函数替代方案

示例:带状态的 Callable

class Multiplier {
  int factor;
  Multiplier(this.factor);

  int call(int value) => value * factor;
}

void main() {
  var triple = Multiplier(3);
  print(triple(5)); // 15
}

这里 triple 是一个对象,但用起来像函数,而且它记住了 factor

成员(字段 & 方法)

一般使用

字段(Field):存储对象的状态。

方法(Method):定义对象的行为。

class Circle {
  double radius;

  Circle(this.radius);

  double area() {
    return 3.14 * radius * radius;
  }
}

静态方法和方法

属于类本身,而不是某个实例

static 关键字声明

无法访问实例成员(只能访问静态成员)

class MathUtils {
  static double pi = 3.14159;

  static double square(double num) {
    return num * num;
  }
}

void main() {
  print(MathUtils.pi);
  print(MathUtils.square(5));
}

Dart 对象方法 vs Java 对象方法 的对比表

特性 Dart Java 说明
实例方法定义 直接写在类里,默认 public 写在类里,需要访问修饰符(publicprivateprotected Dart 没有访问修饰符,靠 _ 控制库级私有
静态方法 static 关键字 static 关键字 用法一致,但 Dart 静态方法不能访问实例成员
抽象方法 abstract 修饰类,在抽象类中定义方法签名 abstract 修饰方法(类必须是抽象类) 两者几乎一致
重写(Override) @override 注解(可省略) @Override 注解(可省略) 推荐都写,方便 IDE 检查
可见性范围 _ 私有(库级别),无 protected public/protected/private/包级可见 Dart 没有 protected
this 可省略(无命名冲突时) 可省略(无命名冲突时) 用法几乎一样
方法参数 支持命名参数 {} 和位置参数 [] 只支持位置参数 Dart 方法定义更灵活
可选参数 有(命名/位置可选) 无(需重载方法) Dart 可以少写很多重载方法
构造方法 ClassName(...),支持命名构造函数、初始化列表、重定向构造 与类名同名,无返回类型,支持重载 Dart 的构造更灵活,支持 ClassName.named()
可调用类(call) 支持 call() 方法,让对象像函数一样调用 不支持 Dart 特有功能
访问成员 obj.method() obj.method() 调用方式相同
必须声明返回值类型 不是必须,可以省略写 dynamic 或不写(默认 dynamic 必须明确写返回类型(void、具体类型等) Dart 更宽松,但强类型项目最好写上
支持可空类型 String? 表示返回值可为 null Java 所有引用类型默认可为 null(无语法提示) Dart 的 ? 更安全,编译期能提醒
void void 表示无返回值 void 表示无返回值 一致
函数作为返回值 支持返回函数对象(高阶函数) Java 8+ 支持 lambda/函数式接口 Dart 更直接

可选参数 vs Java 重载

Dart

class Person {
  void greet({String name = 'Guest'}) {
    print('Hello, $name');
  }
}

Java

class Person {
  void greet() { 
    greet("Guest"); 
  }
  void greet(String name) { 
    System.out.println("Hello, " + name); 
  }
}

➡ Dart 一个方法就能完成 Java 需要方法重载的场景。

Dart 返回类型推断规则表

flowchart TD
    A[方法声明时是否写返回类型?] -->|是| B[按声明的类型编译检查]
    A -->|否| C[方法体中有 return 吗?]
    C -->|否| D[推断为 void]
    C -->|return; 空| D
    C -->|return 值| E[所有 return 值类型相同?]
    E -->|是| F[推断为该类型]
    E -->|否| G[推断为公共父类类型 Object 或 Object?]

不写类型 → 编译器会根据 return 推断类型;没有 return 就是 void

写了类型 → 必须返回该类型的值(null-safety 下路径必须全覆盖)。

Setter 必须 void(可不写,但效果一样)。

OOP: 封装性与GetSet方法

只有两种可见性

Dart 和 Java、C++ 这些语言不太一样,它没有 public / protected / private / default 这样的访问修饰符关键字。它的访问控制规则非常简单:

  • 公有(public):默认就是公有,类的属性和方法外部都能访问
  • 私有(private):在名字前面加下划线 _,只能在**同一个库(library)**内部访问
class Circle {
  double radius;      // 公有属性
  double _diameter;   // 私有属性(仅当前库可见)

  Circle(this.radius) : _diameter = radius * 2;

  double area() {     // 公有方法
    return 3.14 * radius * radius;
  }

  double _perimeter() { // 私有方法
    return 2 * 3.14 * radius;
  }
}

库(library)级别的私有

Dart 里的 _ 私有并不是“类级别的”,而是“库级别的”。

  • 同一个库里,即使是不同类,也能访问 _ 开头的成员
  • 不同库,即使是继承,也不能访问 _ 成员

什么叫做同一个库里

默认情况下

  • 一个 Dart 文件 就是一个独立的库

  • _ 开头的成员只能在这个文件(库)里访问

📂 project/
 ├── main.dart          // 一个库
 ├── utils.dart         // 另一个库

多文件合并成一个库

如果你用 part / part of,可以把多个文件合成一个库,这样它们之间可以互访私有成员。

📂 project/
 ├── my_library.dart    // 主库文件
 ├── src/
 │    ├── file_a.dart   // part of 主库
 │    ├── file_b.dart   // part of 主库

my_library.dart

library my_library;

part 'src/file_a.dart';
part 'src/file_b.dart';

file_a.dart

part of my_library;

String _secret = 'from A';

file_b.dart

part of my_library;

void showSecret() {
  print(_secret); // ✅ 可以访问 file_a.dart 的私有变量
}

简图展示

📂 project/
 ├── main.dart        ← 库 A(只能访问自己 _ 成员)
 ├── utils.dart       ← 库 B(只能访问自己 _ 成员)
 ├── my_library.dart  ← 库 C(包含多个 part 文件)
 │
 └── src/
      ├── file_a.dart ← part of 库 C(可访问库 C 所有 _ 成员)
      ├── file_b.dart ← part of 库 C(可访问库 C 所有 _ 成员)

OOP:封装与隐藏

概念

  • 封装指的是把数据(属性)和操作数据的方法(行为)包装在类中,防止外部随意访问和修改内部细节。
  • 隐藏则是限制外部访问,保证对象的内部状态安全。

Dart 中的封装与隐藏体现:

  • 私有成员:Dart 通过变量或方法名前加 _(下划线)实现库级私有,即只在当前 Dart 文件内可见,外部文件无法访问。
  • 这样可以隐藏类的内部实现细节,只暴露必要的接口。
class BankAccount {
  String _accountNumber;  // 私有字段
  double _balance = 0;    // 私有字段

  BankAccount(this._accountNumber);

  double get balance => _balance;  // 只读公开访问器

  void deposit(double amount) {
    if (amount > 0) {
      _balance += amount;
    }
  }

  void withdraw(double amount) {
    if (amount > 0 && amount <= _balance) {
      _balance -= amount;
    }
  }
}

void main() {
  var account = BankAccount('123456');
  account.deposit(1000);
  print(account.balance);  // 1000
  // account._balance = 5000;  // 报错:私有变量不能访问
}

为什么要把属性设置成私有?

  • 控制访问权限:直接暴露属性给外部意味着外部代码可以随意读写,容易导致对象状态被任意更改,破坏数据完整性。
  • 隐藏内部实现细节:封装的目的是屏蔽实现细节,只暴露接口,让使用者只关心“能做什么”,而不是“怎么做”。
  • 方便后续维护和扩展:如果以后你想对属性访问做额外逻辑(校验、转换、懒加载等),用 getter/setter 就能做到。如果直接暴露属性,后续修改会很困难,甚至破坏向后兼容。

既然有 getter 和 setter,为什么还要私有属性?

getter 和 setter 是“受控访问”,它们是你定义好的访问接口,你可以在里面加入逻辑:

  • 验证数据是否合法
  • 触发事件、通知
  • 懒加载计算

如果属性是公有的,外部随时改值,你没法拦截,也没法做额外处理。

class Person {
  String _name;

  Person(this._name);

  // getter
  String get name => _name;

  // setter,设置时检查非空
  set name(String value) {
    if (value.isEmpty) {
      throw ArgumentError('Name cannot be empty');
    }
    _name = value;
  }
}

void main() {
  var p = Person('Tom');
  p.name = '';  // 会抛异常
}

如果不设置私有,会怎样?

外部可以随意赋值,破坏对象状态,比如:

class Person {
  String name;  // 公开属性
}

void main() {
  var p = Person();
  p.name = "";   // 可能导致后续逻辑错误
}

如果你后来想加入验证,只能改接口,影响调用方。

代码耦合度高,不易维护。

OOP:继承与extend

基本使用

继承:子类(Subclass)从父类(Superclass)获得属性方法,并可以扩展或**重写(override)**它们。

在 Dart 中:

  • extends 表示类继承
  • 只能单继承(一个类只能有一个直接父类)
  • 如果没写 extends,默认继承 Object

extends 的作用

  • 继承父类成员(非私有的属性和方法)
  • 允许子类重写父类方法
  • 可以调用父类的构造函数和方法(通过 super
class Animal {
  String name = '';

  void speak() {
    print('$name makes a sound.');
  }
}

class Dog extends Animal {
  void bark() {
    print('$name says: Woof!');
  }

  // 重写父类方法
  @override
  void speak() {
    print('$name is barking.');
  }
}

void main() {
  var dog = Dog();
  dog.name = 'Buddy';  // 继承来的属性
  dog.speak();         // 调用重写的方法
  dog.bark();          // 调用子类自己的方法
}

调用父类构造函数

class Animal {
  String name;

  Animal(this.name);
  getAnimalInfo() {
    return 'Animal name: $name';
  }
}

class Dog extends Animal {
  int age;
  // 使用初始化列表调用父类构造函数。但是也可以直接使用 super.name 来调用父类的构造函数。
  // Dog(String name, this.age) : super(name);
  // super.name 是指父类的 name 属性,super(name) 是调用父类的构造函数。
  // 如果父类有多个构造函数,可以使用 super.构造函数名()
  Dog(super.name, this.age); // 调用父类构造函数
}

在 Dart 中,父类的 非默认构造函数 必须显式调用。

class Animal {
  String name;

  Animal(this.name);
  Animal.namedConstructor(String namedConstructor) : name = namedConstructor {
    print('Named constructor called with name: $namedConstructor');
  }

  getAnimalInfo() {
    return 'Animal name: $name';
  }
}

class Dog extends Animal {
  int age;
  // 如果父类有多个构造函数,可以使用 super.构造函数名()
  Dog(String name, this.age) : super.namedConstructor(name);
}

Dart 的多继承 with(Mixin)

背景

  • Dart 不支持类的多继承(只能 extends 一个父类)。
  • 但是,有时候你希望一个类获得多个不同类的功能
  • 解决方案:Mixin(混入),用 with 关键字实现类似“多继承”的功能。

基本使用

// 在 Dart 之前的版本,mixin 是可以不加在类名前面的
mixin class Phone {
  void call() {
    print('Phone is calling...');
  }
}

mixin class Android {
  void playStore() {
    print('Google play store');
  }
}

mixin class Ios {
  void appleStore() {
    print('Apply store');
  }
}

class Xiaomi with Phone, Android, Ios {}

void main(List<String> args) {
  var p = Xiaomi();
  p.call(); // 调用 Phone 的方法
  p.playStore(); // 调用 Android 的方法
  p.appleStore(); // 调用 Ios 的方法
}

函数重名冲突

在 Dart 里,with 多个 mixin 时,如果方法名字冲突,会按照 with 的从左到右的顺序 来“覆盖”前面的实现。

换句话说,后面的 mixin 优先级更高

mixin class Phone {
  void call() {
    print('Phone is calling...');
  }
}

mixin class Android {
  void playStore() {
    print('Google play store');
  }

  void call() {
    print('Android phone is calling...');
  }
}

mixin class Ios {
  void appleStore() {
    print('Apply store');
  }

  void call() {
    print('Ios phone is calling...');
  }
}

class Xiaomi with Phone, Android, Ios {}

// 注意:如果有多个 mixin 中有同名方法,Dart 会按照从左到右的顺序调用,最后一个 mixin 的方法会覆盖前面的同名方法。
void main(List<String> args) {
  var p = Xiaomi();
  p.call(); // 最终调用的是最后一个 mixin 的 call 方法,即 Ios 的 call 方法
  p.playStore();
  p.appleStore();
}

mixin 不能构造函数

mixin class Android {
  String version = 'Android 12';
  // mixin 的构造函数不能有参数
  // The class 'Android' can't be used as a mixin because it declares a constructor
  // Android() {
  //   print('Android mixin constructor called');
  // }
  // Android(this.version);
  Android();// 这个构造函数是合法的,因为它没有参数 
  void playStore() {
    print('Google play store');
  }

  void call() {
    print('Android phone is calling...');
  }
}

为什么 mixin 不能有构造函数?

  • Mixin 本质上是用来给类“混入”额外功能的代码块,而不是完整的类。
  • 构造函数涉及对象的初始化和实例化,mixin 不负责实例化,因此不允许写构造函数。
  • 如果你需要构造函数,应该用普通类继承(extends)而不是 mixin。

mixin on 限定条件

什么是 mixin on 限定条件?

  • on 关键字用于限定某个 mixin 只能被指定类型(类)或其子类混入
  • 换句话说,on 限定了 mixin 的“作用范围”,只有继承(或实现)了指定基类的类,才能使用这个 mixin。

正确使用

mixin class Phone {
  void call() {
    print('Phone is calling...');
  }
}

// Android on Phone 表示 Android 是在 Phone 的基础上进行扩展
// 也可以理解为 Android 是 Phone 的一个子类
mixin Android on Phone {
  void playStore() {
    print('Google play store');
  }

  // 必须重新实现 call 方法么? // 不需要,因为 Android 已经继承了 Phone 的 call 方法
  // 如果不想覆盖 Phone 的 call 方法,可以不实现 call 方法
  // 如果想覆盖 Phone 的 call 方法,可以实现自己的 call 方法
  @override
  void call() {
    super.call(); // 调用 Phone 的 call 方法
    print('Android phone is calling...');
  }
}

// 继承了Phone,然后再继承 Android
// 这样 Xiaomi 就可以使用 Phone 和 Android 的方法
class Xiaomi with Phone, Android {} // 这是合法的,因为 Android 是 mixin,可以和 Phone 一起使用

// 继承了 Phone,然后 mixin 了 Android
class Xiaomi4 extends Phone with Android {}

// 实现了 Phone , 然后 mixin 了 Android
class Xiaomi5 implements Phone, Android {
  @override
  void call() {
    // TODO: implement call
  }

  @override
  void playStore() {
    // TODO: implement playStore
  }
}

void main(List<String> args) {
  var p = Xiaomi();
  p.call(); // 调用 Android 的 call 方法
  // Phone is calling...
  // Android phone is calling..
}

class Xiaomi with Phone, Android {}:正确,因为 Xiaomi 先混入了 Phone,再混入Android,所以符合 Android 的限制

class Xiaomi4 extends Phone with Android {}

  • 继承了 Phone,意味着 Xiaomi4Phone 的子类,自动继承了 Phone 的所有成员和实现。
  • 使用了 with Android 混入了 Android 的功能,Android 又限定了 on Phone,这里满足限制。
  • Android 可以调用 super.call(),调用的是 Phonecall()
  • 这是“继承 + mixin”的典型写法,既复用父类代码,也混入其他功能。

class Xiaomi5 implements Phone, Android { ... }

  • 实现了接口 PhoneAndroid,必须实现它们声明的所有方法。
  • implements 不继承实现,只是承诺“实现接口的方法”,所以 call()playStore() 都要自己写实现。
  • 适用于只关心接口规范,不想复用具体实现,或者需要自定义完全不同的实现。

两种错误示范

class Xiaomi2 with Android {}

  • Android 指明了 on Phone,它要求必须混入的类继承或实现 Phone
  • Xiaomi2 只混入了 Android,但它本身没有 extendswith Phone,这违反了 Android 的限制条件。

class Xiaomi3 with Android, Phone {}

  • Android 是一个限定为 on Phone 的 mixin,意思它只能被继承或混入了 Phone 的类使用。但你这里先写了 Android,而后写了 Phone
  • Dart 混入的顺序是从左到右:也就是说,Xiaomi3 先混入了 Android,但此时它还没有继承或混入 Phone,所以编译器会报错说 Android 的约束没满足。

OOP:多态–继承、抽象与接口

基本概念

什么是多态(Polymorphism)

多态指的是:同一个方法调用,根据对象的实际类型不同,会表现出不同的行为

换句话说——父类引用指向不同的子类对象时,调用同名方法能产生不同效果

多态的核心意义:

  1. 解耦调用方与实现方:调用方只依赖父类(或接口)类型,不关心具体子类是谁。
  2. 可扩展性强:增加新子类不需要改调用方代码,只需保证实现父类方法。
  3. 统一接口,差异实现:比如 draw() 方法:圆、矩形、三角形实现不同,但调用方都用 Shape 接口调用。

Dart 中体现多态的语法

  • 继承(extends)
  • 抽象类(abstract class)
  • 接口实现(implements)

继承(extends)

父类定义通用方法,子类重写(override)它

class Animal {
  void speak() {
    print('Animal sound');
  }
}

class Dog extends Animal {
  @override
  void speak() => print('Dog barks');
}

class Cat extends Animal {
  @override
  void speak() => print('Cat meows');
}
void main() {
  Animal a = Dog(); // 父类引用指向子类的实例
  a.speak(); // Dog barks(运行时看对象类型,不是引用类型)
}

抽象类(abstract class)

什么是抽象类

抽象类是 不能直接实例化 的类,通常用来 定义一组通用的属性和方法规范,并让子类去实现它们。

它的关键特征:

  • 可以包含抽象方法(没有方法体)
  • 也可以包含普通方法(有方法体)
  • 只能被继承(extends)或者实现(implements
  • 抽象方法必须由子类实现

语法:

abstract class Animal {
  void speak(); // 抽象方法(无方法体)
  void sleep() { // 普通方法(有实现)
    print('Sleeping...');
  }
}

为什么要有抽象类

  1. 定义规范:父类规定所有子类必须实现哪些方法(比如接口要求)。
  2. 减少重复代码:抽象类可以写通用逻辑(普通方法),子类直接继承使用。
  3. 多态支持:可以用父类类型去引用不同的子类实例,实现多态。

基本例子

abstract class Animal {
  void speak(); // 抽象方法
  void eat() {
    print('Animal is eating');
  }
}

class Dog extends Animal {
  @override
  void speak() {
    print('Dog barks');
  }
}

void main() {
  // Animal a = Animal(); ❌ 报错:不能直接实例化
  Animal dog = Dog();
  dog.eat();   // Animal is eating
  dog.speak(); // Dog barks
}

抽象类 与 多态

void makeSpeak(Animal animal) {
  animal.speak();
}

void main() {
  makeSpeak(Dog()); // Dog barks
  makeSpeak(Cat()); // Cat meows
}

这里 makeSpeak 只认 Animal,但实际调用的是子类版本 → 多态。

接口实现(implements

什么是 Dart 的 implements

在 Dart 里,任何类都可以当作接口来实现,不需要专门用 interface 关键字。

implements 关键字表示 实现一个类或抽象类定义的所有成员,并且必须自己重新写一份实现(即使父类里已经有默认实现,也不能直接继承用)。

基本语法:

class 接口类 { ... }
class 实现类 implements 接口类 { ... }

把类当接口用

class Person {
  String name = 'Unknown';   // 公共属性
  int _age = 0;              // 私有属性(库内可见)

  void greet() {             // 公共方法
    print('Hello, my name is $name.');
  }

  void _showAge() {          // 私有方法(库内可见)
    print('I am $_age years old.');
  }
}

// 不再同一个文件夹:
import 'Person.dart';
class Student implements Person {
  @override
  String name = '';
  int _age = 0; // 自己的私有字段(和 Person 的 _age 无关)


  // 必须实现 greet 方法
  @override
  void greet() {
    print('Hi, I am $_name, a student.');
  }
}
类成员类型 implements 是否需要实现 原因
公共属性 (name) ✅ 必须实现 公共 API 属于接口的一部分
私有属性 (_age) ❌ 不需要实现 私有成员在库外不可见,不属于接口规范
公共方法 (greet()) ✅ 必须实现 公共 API 属于接口的一部分
私有方法 (_showAge()) ❌ 不需要实现 私有成员在库外不可见,不属于接口规范

私有成员(以 _ 开头)在 库外(不同文件且不同库)不可见,不会进入 implements 约束。

公共成员(属性和方法)会被 implements 转换成需要实现的抽象方法/getter/setter。

implements 是完全按接口契约来,不会继承任何实现,必须自己重写。

把抽象类做接口

abstract class IPerson {
  String name;
  int age;

  IPerson(this.name, this.age);

  String info() {
    return 'Name: $name, Age: $age';
  }
}

class Teacher implements IPerson {
  @override
  String name;

  @override
  int age;

  Teacher(this.name, this.age);

  @override
  String info() {
    return 'Teacher -> Name: $name, Age: $age';
  }
}

class Student implements IPerson {
  @override
  int age;

  @override
  String name;

  Student(this.name, this.age);

  @override
  String info() {
    return 'Student -> Name: $name, Age: $age';
  }
}

// 调用这个方法:体现接口的多态性
void makePersonInfo(IPerson user) => print(user.info());
void main(List<String> args) {
  var t = Teacher('ducafecat', 99);
  t.age = 10; // 可以修改 age,因为 Teacher 实现了 IPerson 接口
  t.name = 'abc'; // 可以修改 name,因为 Teacher 实现了 IPerson 接口
  makePersonInfo(t); // 输出: Teacher -> Name: abc, Age: 10

  var s = Student('hans', 66);
  s.age = 20; // 可以修改 age,因为 Student 实现了 IPerson 接口
  s.name = 'def'; // 可以修改 name,因为 Student 实现了 IPerson 接口
  makePersonInfo(s); // 输出: Student -> Name: def, Age: 20
}

履行多接口

// 定义一个接口 IPerson 和 ISchool
abstract class IPerson {
  String name;
  int age;

  IPerson(this.name, this.age);

  String info() {
    return 'Name: $name, Age: $age';
  }
}

abstract class ISchool {
  int grade;

  ISchool(this.grade);

  String schoolInfo() {
    return 'Grade: $grade';
  }
}

// Teacher 实现了 IPerson 接口
class Teacher implements IPerson {
  @override
  String name;

  @override
  int age;

  Teacher(this.name, this.age);

  @override
  String info() {
    return 'Teacher -> Name: $name, Age: $age';
  }
}

// Student 实现了 IPerson 和 ISchool 接口
// 注意:Student 类必须实现所有接口中的属性和方法
class Student implements IPerson, ISchool {
  @override
  int age;

  @override
  String name;

  @override
  int grade;

  Student(this.name, this.age, this.grade);

  @override
  String info() {
    return 'Student -> Name: $name, Age: $age';
  }

  @override
  String schoolInfo() {
    return 'School -> Name: $name, Age: $age, Grade: $grade';
  }
}

// 调用这个方法:体现接口的多态性
// 这里的 IPerson 和 ISchool 是接口
void makePersonInfo(IPerson user) => print(user.info());
void makeSchoolInfo(ISchool user) => print(user.schoolInfo());

void main(List<String> args) {
  var t = Teacher('ducafecat', 99);
  makePersonInfo(t); // 输出: Teacher -> Name: ducafecat, Age: 99

  var s = Student('hans', 66, 5);
  makePersonInfo(s); // 输出: Student -> Name: hans, Age: 66
  makeSchoolInfo(s); // 输出: School -> Name: hans, Age: 66, Grade: 5
}

工厂函数

什么是工厂函数?

  • 工厂函数是一种特殊的构造函数,用 factory 关键字声明。
  • 不一定返回新实例,可以返回已有对象、子类实例,或者缓存的对象。
  • 本质上是一个静态方法,负责“生产”对象,但可以灵活控制对象的创建过程。

工厂函数怎么使用?

  • 在类中定义 factory 构造函数,返回一个类实例。

  • 通常配合私有构造函数 _ 使用,控制实例化过程。

单例模式

class Logger {
  Logger._internal(); // 私有构造函数

  static final Logger _instance = Logger._internal();
  
  // 工厂构造函数,返回单例实例
  factory Logger() {
    return _instance; // 返回单例
  }
}

void main() {
  var a = Logger(); // 这里调用了工厂构造函数
  var b = Logger(); // 再次调用工厂构造函数
  print(identical(a, b)); // true,a 和 b 是同一个实例
}

Logger._internal(); 是什么?

  • 这是 一个私有命名构造函数
  • _internal构造函数的名字,可以是任意合法标识符。
  • 在 Dart 中,前缀 _ 表示私有成员,只在当前库(文件)内可见,外部不能访问。
  • 它的作用是:阻止外部直接调用默认构造函数创建实例,只能通过工厂函数来创建/获取实例

static final Logger _instance = Logger._internal(); 是什么?

  • 这是 定义了一个静态的、最终的(不可变)实例变量,存储一个 Logger 类的实例。
  • 这个实例是通过调用私有的命名构造函数 Logger._internal() 创建的。
  • 因为 _instancestatic,它属于类本身而非某个对象,整个程序中只有这一个实例。

单例模式的作用和意义

作用 说明
控制资源访问 例如数据库连接、日志记录器等需要唯一实例避免冲突
节省内存和性能开销 避免重复创建对象,降低资源消耗
全局共享状态 保证多个组件访问同一个对象,数据保持一致
避免不一致和错误 避免因为多个实例造成状态不同步或不一致的问题

返回不同子类实例

abstract class Animal {
  factory Animal(String type) {
    if (type == 'dog') return Dog();
    if (type == 'cat') return Cat();
    throw 'Unknown animal';
  }
  void speak();
}

class Dog implements Animal {
  void speak() => print('Woof!');
}

class Cat implements Animal {
  void speak() => print('Meow!');
}

void main() {
  Animal a = Animal('dog');
  a.speak(); // Woof!
}
  • 在没有工厂函数之前:用户自己决定要哪个类的实例。但是客户端必须知道具体类的构造,耦合高。
  • 有了工程方法之后:
    • 客户端只调用一个构造方法 Animal(),不关心具体实现。
    • 可灵活切换返回不同子类实例,符合开放封闭原则。

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1909773034@qq.com

×

喜欢就点赞,疼爱就打赏