Dart: Dart 类与对象 — 面向对象编程核心

类是对象的蓝图 — 好的蓝图造出好的对象,好的对象组成好的系统。

1. 你将学到


2. 一个开发者的真实故事

(1) 痛点:数据模型混乱导致逻辑分散

Bob 的 DataPipeline 早期用 Map 处理所有数据,没有类型安全。order['amout'](拼写错误)运行时返回 null 而不是报错,导致 100,000 条订单的金额统计全部为 0。更糟的是,创建订单的逻辑散布在 5 个文件中,没有统一验证。

(2) 类的解法

用类定义数据模型,构造函数统一验证逻辑,类型系统在编译时捕获拼写错误和类型不匹配。

DART
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;
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

(3) 收益


3. 类定义基础

(1) 类的结构

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

:基本类定义

DART
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
}
TEXT 📖 仅展示
> **输出:** 在本地 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 领域模型

100%
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
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

:默认构造函数与命名构造函数

DART
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)';
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

:工厂构造函数

DART
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}');
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

:重定向构造函数

DART
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);
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

:常量构造函数

DART
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
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
构造函数类型 语法 特点 场景
默认 ClassName() 自动生成或自定义 标准创建
命名 ClassName.name() 多种创建方式 fromCsv, now
工厂 factory ClassName() 可返回子类或缓存实例 单例、缓存
重定向 : this() 委托给主构造函数 便捷创建
常量 const ClassName() 编译时创建,不可变 配置常量

5. 继承与方法重写

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

:继承体系

DART
// 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
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

6. 抽象类与接口

(1) 抽象类

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

:抽象类与实现

DART
// 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');
  }
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

(2) 隐式接口

Dart 的每个类都隐式定义了一个接口,包含所有实例方法和字段。其他类可以 implements 这个接口。

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

:隐式接口实现

DART
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;
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
维度 abstract class interface (implements) mixin
实例化 不可 不可
方法实现 可有可无 必须全部重实现 可有可无
多继承 单继承 可多实现 可多混入
构造函数

7. 完整示例:DataPipeline 领域模型

DART
// ============================================
// 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)');
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

输出:

TEXT 📖 仅展示
=== 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) 合法,可以在解析失败时返回默认实例而不是抛异常。


📖 小节


📝 作业

  1. 基础题(难度⭐):定义一个 Product 类,包含 name、price、category 字段,添加一个 formatUSD() 方法和 isExpensive getter,创建 3 个产品实例并打印。
  2. 进阶题(难度⭐⭐):为 Order 类添加命名构造函数 Order.fromMap(从 Map 创建)和工厂构造函数 Order.safe(解析失败时返回默认订单),测试边界情况。
  3. 挑战题(难度⭐⭐⭐):设计一个 ReportGenerator 抽象类,创建 JsonReport 和 CsvReport 两个子类,实现多态的报表生成。再创建一个 MockReport 用 implements 实现同一个接口,用于测试。

← 上一课 | 下一课 →

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏