Dart: Dart 类与对象 — 面向对象编程核心
类是对象的蓝图 — 好的蓝图造出好的对象,好的对象组成好的系统。
1. 你将学到
- 类定义、属性与方法的声明
- 构造函数:默认 / 命名 / 工厂 / 重定向 / 常量构造
- 继承与方法重写(@override)
- 抽象类与接口(implicit interface)
- Bob 场景:DataPipeline 中的 Order / Product / Customer 模型类
2. 一个开发者的真实故事
(1) 痛点:数据模型混乱导致逻辑分散
Bob 的 DataPipeline 早期用 Map 处理所有数据,没有类型安全。order['amout'](拼写错误)运行时返回 null 而不是报错,导致 100,000 条订单的金额统计全部为 0。更糟的是,创建订单的逻辑散布在 5 个文件中,没有统一验证。
(2) 类的解法
用类定义数据模型,构造函数统一验证逻辑,类型系统在编译时捕获拼写错误和类型不匹配。
class Order {
final String id;
final double amount;
final DateTime date;
Order({required this.id, required this.amount, required this.date})
: assert(amount > 0, 'Amount must be positive');
double calcTax(double rate) => amount * rate;
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
(3) 收益
- 类型错误从运行时提前到编译时,bug 减少 80%
- 构造函数统一数据验证,不再遗漏检查
- 继承和接口让代码复用率提升 60%
3. 类定义基础
(1) 类的结构
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:基本类定义
class Product {
// Fields
final String name;
final double price;
String category;
// Constructor
Product({required this.name, required this.price, this.category = 'General'});
// Method
String formatPrice() => '\$${price.toStringAsFixed(2)} USD';
// Getter
bool get isExpensive => price >= 1000;
// Override toString
@override
String toString() => 'Product($name, ${formatPrice()}, $category)';
}
void main() {
final product = Product(name: 'Laptop', price: 1299.99, category: 'Electronics');
print(product); // Product(Laptop, $1299.99 USD, Electronics)
print(product.isExpensive); // true
print(product.formatPrice()); // $1299.99 USD
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
| 类成员 | 声明语法 | 说明 |
|---|---|---|
| 字段 | Type name |
实例变量 |
| 构造函数 | ClassName() |
创建对象 |
| 方法 | returnType name() {} |
实例方法 |
| Getter | Type get name => expr |
计算属性 |
| Setter | set name(Type value) |
赋值拦截 |
4. 五种构造函数
(1) DataPipeline 领域模型
classDiagram
class Order {
+String id
+double amount
+DateTime date
+calcTax(double rate) double
}
class Product {
+String name
+double price
+formatUSD() String
}
class Customer {
+String name
+List~Order~ orders
}
Customer "1" --> "*" Order : has
Order "1" --> "*" Product : contains
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:默认构造函数与命名构造函数
class Order {
final String id;
final double amount;
final DateTime date;
String status;
// Default constructor
Order({required this.id, required this.amount, required this.date, this.status = 'pending'});
// Named constructor - create from CSV row
Order.fromCsv(String csvLine)
: id = csvLine.split(',')[0],
amount = double.parse(csvLine.split(',')[1]),
date = DateTime.parse(csvLine.split(',')[2]),
status = 'pending';
// Named constructor - create with default date
Order.now({required this.id, required this.amount})
: date = DateTime.now(),
status = 'pending';
double calcTax(double rate) => amount * rate;
@override
String toString() => 'Order($id, \$${amount.toStringAsFixed(2)}, $status)';
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:工厂构造函数
class DataSource {
final String type;
final String connection;
// Private constructor
DataSource._internal(this.type, this.connection);
// Factory constructor - returns cached or custom instance
factory DataSource.create({required String type, required String connection}) {
return DataSource._internal(type, connection);
}
// Factory with default configurations
factory DataSource.api(String endpoint) =>
DataSource._internal('api', endpoint);
factory DataSource.file(String path) =>
DataSource._internal('file', path);
factory DataSource.database(String connectionString) =>
DataSource._internal('database', connectionString);
}
void main() {
final api = DataSource.api('https://api.example.com/orders');
final file = DataSource.file('/data/orders.csv');
print('${api.type}: ${api.connection}');
print('${file.type}: ${file.connection}');
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:重定向构造函数
class Report {
final String title;
final String format;
final int recordCount;
// Main constructor
Report({required this.title, this.format = 'json', this.recordCount = 0});
// Redirecting constructors
Report.json(String title, int count) : this(title: title, format: 'json', recordCount: count);
Report.csv(String title, int count) : this(title: title, format: 'csv', recordCount: count);
Report.html(String title, int count) : this(title: title, format: 'html', recordCount: count);
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:常量构造函数
class Config {
final String appName;
final int maxRecords;
// const constructor - all fields must be final
const Config({this.appName = 'DataPipeline', this.maxRecords = 1000000});
}
void main() {
const config1 = Config(); // Compile-time constant
const config2 = Config(maxRecords: 500000);
print(identical(config1, config2)); // false (different values)
print(config1.maxRecords); // 1000000
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
| 构造函数类型 | 语法 | 特点 | 场景 |
|---|---|---|---|
| 默认 | ClassName() |
自动生成或自定义 | 标准创建 |
| 命名 | ClassName.name() |
多种创建方式 | fromCsv, now |
| 工厂 | factory ClassName() |
可返回子类或缓存实例 | 单例、缓存 |
| 重定向 | : this() |
委托给主构造函数 | 便捷创建 |
| 常量 | const ClassName() |
编译时创建,不可变 | 配置常量 |
5. 继承与方法重写
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:继承体系
// Base class
class DataProcessor {
final String name;
DataProcessor(this.name);
// Method to be overridden
String process(String input) => 'Processing $input with $name';
// Non-overridable method
String getStatus() => 'Ready';
@override
String toString() => '$name Processor';
}
// Subclass
class OrderProcessor extends DataProcessor {
final double taxRate;
OrderProcessor(this.taxRate) : super('Order');
@override
String process(String input) {
final amount = double.tryParse(input) ?? 0;
final taxed = amount * (1 + taxRate);
return 'Order processed: \$${taxed.toStringAsFixed(2)} USD';
}
}
// Another subclass
class ProductProcessor extends DataProcessor {
ProductProcessor() : super('Product');
@override
String process(String input) => 'Product cataloged: $input';
}
void main() {
final orderProc = OrderProcessor(0.08);
print(orderProc.process('1500')); // Order processed: $1620.00 USD
print(orderProc.getStatus()); // Ready
final productProc = ProductProcessor();
print(productProc.process('Laptop')); // Product cataloged: Laptop
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
6. 抽象类与接口
(1) 抽象类
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:抽象类与实现
// Abstract class - cannot be instantiated
abstract class ReportGenerator {
final String format;
ReportGenerator(this.format);
// Abstract method - must be implemented
String generate(List<Map<String, dynamic>> data);
// Concrete method - shared implementation
String header(String title) => '=== $title ($format) ===';
}
class JsonReportGenerator extends ReportGenerator {
JsonReportGenerator() : super('json');
@override
String generate(List<Map<String, dynamic>> data) {
final buffer = StringBuffer();
buffer.writeln(header('DataPipeline Report'));
for (final entry in data) {
buffer.writeln(' ${entry['id']}: ${entry['amount']}');
}
return buffer.toString();
}
}
class CsvReportGenerator extends ReportGenerator {
CsvReportGenerator() : super('csv');
@override
String generate(List<Map<String, dynamic>> data) {
final lines = ``<String>``['id,amount'];
for (final entry in data) {
lines.add('${entry['id']},${entry['amount']}');
}
return lines.join('\n');
}
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
(2) 隐式接口
Dart 的每个类都隐式定义了一个接口,包含所有实例方法和字段。其他类可以 implements 这个接口。
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:隐式接口实现
class FileDataSource {
final String path;
FileDataSource(this.path);
List``<String>`` readLines() => ['line1', 'line2'];
bool get exists => true;
}
// Implement the implicit interface of FileDataSource
class MockDataSource implements FileDataSource {
@override
String path = '/mock/data.csv';
@override
List``<String>`` readLines() => ['mock1', 'mock2', 'mock3'];
@override
bool get exists => true;
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
| 维度 | abstract class | interface (implements) | mixin |
|---|---|---|---|
| 实例化 | 不可 | — | 不可 |
| 方法实现 | 可有可无 | 必须全部重实现 | 可有可无 |
| 多继承 | 单继承 | 可多实现 | 可多混入 |
| 构造函数 | 有 | 无 | 无 |
7. 完整示例:DataPipeline 领域模型
// ============================================
// DataPipeline Domain Model
// Order, Product, Customer with full OOP
// ============================================
class Product {
final String id;
final String name;
final double price;
final String category;
const Product({
required this.id,
required this.name,
required this.price,
this.category = 'General',
});
String formatUSD() => '\$${price.toStringAsFixed(2)} USD';
@override
String toString() => 'Product($name, ${formatUSD()})';
}
class Order {
final String id;
final double amount;
final DateTime date;
String status;
final List``<Product>`` products;
Order({
required this.id,
required this.amount,
required this.date,
this.status = 'pending',
this.products = const [],
}) : assert(amount > 0, 'Amount must be positive');
Order.now({required this.id, required this.amount, this.products = const []})
: date = DateTime.now(),
status = 'pending';
double calcTax(double rate) => amount * rate;
double calcTotal(double taxRate) => amount + calcTax(taxRate);
@override
String toString() => 'Order($id, \$${amount.toStringAsFixed(2)}, $status, ${products.length} items)';
}
class Customer {
final String name;
final String email;
final List``<Order>`` orders;
Customer({required this.name, required this.email, List``<Order>``? orders})
: orders = orders ?? [];
double get totalSpent => orders.fold(0.0, (sum, o) => sum + o.amount);
int get orderCount => orders.length;
double get averageOrderValue => orderCount > 0 ? totalSpent / orderCount : 0;
void addOrder(Order order) => orders.add(order);
String tier() => switch (totalSpent) {
>= 50000 => 'Enterprise',
>= 10000 => 'Premium',
>= 1000 => 'Standard',
_ => 'Free',
};
@override
String toString() => 'Customer($name, ${tier()}, ${orderCount} orders, \$${totalSpent.toStringAsFixed(2)} total)';
}
void main() {
final laptop = Product(id: 'P001', name: 'Laptop', price: 1299.99, category: 'Electronics');
final mouse = Product(id: 'P002', name: 'Mouse', price: 29.99, category: 'Electronics');
final alice = Customer(name: 'Alice', email: 'alice@example.com');
final bob = Customer(name: 'Bob', email: 'bob@example.com');
alice.addOrder(Order(id: 'ORD-001', amount: 1500.0, date: DateTime(2024, 1, 15), products: [laptop]));
alice.addOrder(Order(id: 'ORD-002', amount: 3200.0, date: DateTime(2024, 2, 20)));
bob.addOrder(Order(id: 'ORD-003', amount: 890.0, date: DateTime(2024, 3, 10), products: [mouse]));
print('=== DataPipeline Customers ===');
print(alice);
print(bob);
print('\n--- Alice Orders ---');
for (final order in alice.orders) {
print(' $order (tax: \$${order.calcTax(0.08).toStringAsFixed(2)} USD)');
}
print('\nTier Summary:');
print(' Alice: ${alice.tier()} (\$${alice.totalSpent.toStringAsFixed(2)} total)');
print(' Bob: ${bob.tier()} (\$${bob.totalSpent.toStringAsFixed(2)} total)');
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
输出:
=== DataPipeline Customers ===
Customer(Alice, Premium, 2 orders, $4700.00 total)
Customer(Bob, Standard, 1 orders, $890.00 total)
--- Alice Orders ---
Order(ORD-001, $1500.00, pending, 1 items) (tax: $120.00 USD)
Order(ORD-002, $3200.00, pending, 0 items) (tax: $256.00 USD)
Tier Summary:
Alice: Premium ($4700.00 total)
Bob: Standard ($890.00 total)
❓ 常见问题
Q:Dart 有多继承吗? A:没有。Dart 只支持单继承(extends),但可以通过 implements 实现多个接口,或通过 with 混入多个 mixin。
Q:工厂构造函数和普通构造函数的区别? A:普通构造函数总是创建新实例;工厂构造函数可以返回缓存的实例、子类实例,甚至执行复杂逻辑后再决定返回什么。用 factory 关键字声明。
Q:const 构造函数有什么用? A:const 构造函数创建编译时常量对象,相同参数的 const 构造会返回同一个实例(identical)。适合配置类、枚举值等不可变对象。
Q:abstract class 和 interface 怎么选? A:需要共享实现代码时用 abstract class extends;只需要定义契约时用 interface implements。Dart 没有专门的 interface 关键字,每个类本身就是接口。
Q:什么时候用 @override? A:重写父类方法时应该加 @override。虽然不是强制的,但加上了可以让编译器帮你检查是否正确重写(如果父类没有这个方法会报错)。
Q:Dart 的 assert 在生产环境有效吗? A:无效。assert 只在 debug 模式执行,release 模式被忽略。用于开发期验证,不能替代正式的参数校验。
Q:命名构造函数可以是工厂的吗? A:可以。如
factory Order.fromCsv(String line)合法,可以在解析失败时返回默认实例而不是抛异常。
📖 小节
- 类定义包含字段、构造函数、方法、getter/setter,是数据模型的核心
- 五种构造函数各有用途:命名(多创建方式)、工厂(缓存/子类)、重定向(便捷)、常量(不可变)
- 单继承 + 多接口实现 + 多 mixin 混入,灵活组合
- 抽象类提供共享实现,隐式接口定义契约
- DataPipeline 的 Order/Product/Customer 构成领域模型,支撑业务逻辑
📝 作业
- 基础题(难度⭐):定义一个
Product类,包含 name、price、category 字段,添加一个formatUSD()方法和isExpensivegetter,创建 3 个产品实例并打印。 - 进阶题(难度⭐⭐):为
Order类添加命名构造函数Order.fromMap(从 Map 创建)和工厂构造函数Order.safe(解析失败时返回默认订单),测试边界情况。 - 挑战题(难度⭐⭐⭐):设计一个
ReportGenerator抽象类,创建 JsonReport 和 CsvReport 两个子类,实现多态的报表生成。再创建一个 MockReport 用 implements 实现同一个接口,用于测试。