对象和类
类(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 对象的创建过程分两步:
- 初始化阶段
- Dart 会先调用初始化列表,把字段(实例变量)赋上值。
- 这个阶段必须保证 非空字段(non-nullable fields) 已经有值。
- 构造函数体执行阶段
- 初始化完成后,才会执行
{ ... }
里的代码。
- 初始化完成后,才会执行
因为 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
}
执行顺序:
调用
Point(3, 4)
初始化列表先执行 →
x = 3
,y = 4
然后执行构造函数体
{ 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 |
写在类里,需要访问修饰符(public 、private 、protected ) |
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
,意味着Xiaomi4
是Phone
的子类,自动继承了Phone
的所有成员和实现。 - 使用了
with Android
混入了Android
的功能,Android
又限定了on Phone
,这里满足限制。 Android
可以调用super.call()
,调用的是Phone
的call()
。- 这是“继承 + mixin”的典型写法,既复用父类代码,也混入其他功能。
class Xiaomi5 implements Phone, Android { ... }
- 实现了接口
Phone
和Android
,必须实现它们声明的所有方法。 implements
不继承实现,只是承诺“实现接口的方法”,所以call()
和playStore()
都要自己写实现。- 适用于只关心接口规范,不想复用具体实现,或者需要自定义完全不同的实现。
两种错误示范
class Xiaomi2 with Android {}
Android
指明了on Phone
,它要求必须混入的类继承或实现Phone
。Xiaomi2
只混入了Android
,但它本身没有extends
或with
Phone
,这违反了Android
的限制条件。
class Xiaomi3 with Android, Phone {}
Android
是一个限定为on Phone
的 mixin,意思它只能被继承或混入了Phone
的类使用。但你这里先写了Android
,而后写了Phone
。- Dart 混入的顺序是从左到右:也就是说,
Xiaomi3
先混入了Android
,但此时它还没有继承或混入Phone
,所以编译器会报错说Android
的约束没满足。
OOP:多态–继承、抽象与接口
基本概念
什么是多态(Polymorphism)
多态指的是:同一个方法调用,根据对象的实际类型不同,会表现出不同的行为。
换句话说——父类引用指向不同的子类对象时,调用同名方法能产生不同效果。
多态的核心意义:
- 解耦调用方与实现方:调用方只依赖父类(或接口)类型,不关心具体子类是谁。
- 可扩展性强:增加新子类不需要改调用方代码,只需保证实现父类方法。
- 统一接口,差异实现:比如
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...');
}
}
为什么要有抽象类
- 定义规范:父类规定所有子类必须实现哪些方法(比如接口要求)。
- 减少重复代码:抽象类可以写通用逻辑(普通方法),子类直接继承使用。
- 多态支持:可以用父类类型去引用不同的子类实例,实现多态。
基本例子
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()
创建的。 - 因为
_instance
是static
,它属于类本身而非某个对象,整个程序中只有这一个实例。
单例模式的作用和意义
作用 | 说明 |
---|---|
控制资源访问 | 例如数据库连接、日志记录器等需要唯一实例避免冲突 |
节省内存和性能开销 | 避免重复创建对象,降低资源消耗 |
全局共享状态 | 保证多个组件访问同一个对象,数据保持一致 |
避免不一致和错误 | 避免因为多个实例造成状态不同步或不一致的问题 |
返回不同子类实例
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