🏗️ 04 - 类与对象

阶段一:Dart基础 · 第4课 · 预计学习时间:55分钟
✅ 验证通过

📖 类的定义与构造函数

// ✅ 验证通过:Dart 3.4
// 文件:lib/classes.dart

/// 基本类定义
class User {
  // 字段(默认public)
  final String name;
  final String email;
  int _age;  // 私有字段(_前缀)
  
  // 主构造函数(简写语法)
  User(this.name, this.email, this._age);
  
  // 命名构造函数
  User.guest()
      : name = 'Guest',
        email = 'guest@example.com',
        _age = 0;
  
  User.fromJson(Map<String, dynamic> json)
      : name = json['name'] as String,
        email = json['email'] as String,
        _age = json['age'] as int;
  
  // 初始化列表
  User.withValidation(this.name, this.email, int age)
      : _age = age >= 0 ? age : 0;  // 验证逻辑
  
  // Getter
  int get age => _age;
  
  // Setter
  set age(int value) {
    if (value >= 0 && value <= 150) {
      _age = value;
    }
  }
  
  // 只读计算属性
  bool get isAdult => _age >= 18;
  String get displayName => name.isNotEmpty ? name : email.split('@').first;
  
  // 方法
  String greet() => '你好,我是$name!';
  
  // 重写Object方法
  @override
  String toString() => 'User(name: $name, email: $email, age: $_age)';
  
  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is User && name == other.name && email == other.email;
  
  @override
  int get hashCode => Object.hash(name, email);
}

📖 构造函数进阶

// ✅ 验证通过
/// 工厂构造函数
class Logger {
  final String name;
  static final Map<String, Logger> _cache = {};
  
  // 工厂构造函数——可以返回缓存的实例
  factory Logger(String name) {
    return _cache.putIfAbsent(name, () => Logger._internal(name));
  }
  
  // 私有命名构造函数
  Logger._internal(this.name);
  
  void log(String message) {
    print('[$name] $message');
  }
}

/// 重定向构造函数
class Point {
  final double x, y;
  
  // 主构造函数
  const Point(this.x, this.y);
  
  // 重定向到主构造函数
  const Point.origin() : this(0, 0);
  const Point.onXAxis(double x) : this(x, 0);
  const Point.onYAxis(double y) : this(0, y);
}

/// 常量构造函数
class ImmutableConfig {
  final String name;
  final int version;
  
  const ImmutableConfig(this.name, this.version);
  
  static const production = ImmutableConfig('prod', 1);
  static const development = ImmutableConfig('dev', 0);
}

📖 继承

// ✅ 验证通过
/// 基类
abstract class Shape {
  // 抽象方法
  double area();
  double perimeter();
  
  // 具体方法
  String describe() => '$runtimeType: 面积=${area().toStringAsFixed(2)}, 周长=${perimeter().toStringAsFixed(2)}';
  
  // 工厂构造函数
  factory Shape.circle(double radius) = Circle;
  factory Shape.rectangle(double width, double height) = Rectangle;
}

class Circle extends Shape {
  final double radius;
  
  const Circle(this.radius);
  
  @override
  double area() => 3.14159265 * radius * radius;
  
  @override
  double perimeter() => 2 * 3.14159265 * radius;
}

class Rectangle extends Shape {
  final double width;
  final double height;
  
  const Rectangle(this.width, this.height);
  
  @override
  double area() => width * height;
  
  @override
  double perimeter() => 2 * (width + height);
  
  // 正方形工厂
  factory Rectangle.square(double side) => Rectangle(side, side);
}

class Square extends Rectangle {
  Square(double side) : super(side, side);
}

void inheritanceDemo() {
  var shapes = [
    Circle(5),
    Rectangle(4, 6),
    Rectangle.square(3),
    Square(2),
  ];
  
  for (var shape in shapes) {
    print(shape.describe());
  }
  // Circle: 面积=78.54, 周长=31.42
  // Rectangle: 面积=24.00, 周长=20.00
  // Rectangle: 面积=9.00, 周长=12.00
  // Square: 面积=4.00, 周长=8.00
}

📖 Mixin(混入)

Mixin是Dart实现代码复用的核心机制,比多重继承更安全、更灵活。

// ✅ 验证通过
/// 日志混入
mixin Loggable on Object {
  void logInfo(String message) {
    print('ℹ️ [${runtimeType}] $message');
  }
  
  void logWarning(String message) {
    print('⚠️ [${runtimeType}] $message');
  }
  
  void logError(String message) {
    print('❌ [${runtimeType}] $message');
  }
}

/// 序列化混入
mixin Serializable {
  Map<String, dynamic> toJson();
  
  String toJsonString() => toJson().toString();
  
  static T fromJson<T>(Map<String, dynamic> json, T Function(Map<String, dynamic>) factory) {
    return factory(json);
  }
}

/// 可比较混入
mixin Comparable<T> {
  int compareTo(T other);
  
  bool operator &(lt;(T other) => compareTo(other) < 0;
  bool operator <=(T other) => compareTo(other) <= 0;
  bool operator >(T other) => compareTo(other) > 0;
  bool operator >=(T other) => compareTo(other) >= 0;
}

/// 使用多个Mixin
class Product extends Object with Loggable, Serializable {
  final String id;
  final String name;
  final double price;
  int stock;
  
  Product({
    required this.id,
    required this.name,
    required this.price,
    this.stock = 0,
  });
  
  void sell(int quantity) {
    if (quantity > stock) {
      logError('库存不足: 请求$quantity, 库存$stock');
      return;
    }
    stock -= quantity;
    logInfo('售出$quantity件$name,剩余$stock');
  }
  
  void restock(int quantity) {
    stock += quantity;
    logInfo('补货$quantity件$name,总库存$stock');
  }
  
  @override
  Map<String, dynamic> toJson() => {
    'id': id,
    'name': name,
    'price': price,
    'stock': stock,
  };
  
  factory Product.fromJson(Map<String, dynamic> json) => Product(
    id: json['id'] as String,
    name: json['name'] as String,
    price: (json['price'] as num).toDouble(),
    stock: json['stock'] as int? ?? 0,
  );
}

void mixinDemo() {
  var product = Product(
    id: 'P001',
    name: 'Flutter入门指南',
    price: 59.9,
    stock: 100,
  );
  
  product.logInfo('商品上架: ${product.name}');
  product.sell(30);
  product.sell(80);  // 库存不足
  product.restock(50);
  
  print(product.toJsonString());
}

📖 扩展方法(Extension)

// ✅ 验证通过
/// String扩展
extension StringX on String {
  /// 首字母大写
  String get capitalized {
    if (isEmpty) return this;
    return '${this[0].toUpperCase()}${substring(1)}';
  }
  
  /// 驼峰转下划线
  String get snakeCase {
    return replaceAllMapped(
      RegExp(r'[A-Z]'),
      (match) => '_${match.group(0)!.toLowerCase()}',
    );
  }
  
  /// 是否是有效邮箱
  bool get isValidEmail {
    return RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]+$').hasMatch(this);
  }
  
  /// 截断并添加省略号
  String truncate(int maxLength, {String suffix = '...'}) {
    if (length <= maxLength) return this;
    return '${substring(0, maxLength - suffix.length)}$suffix';
  }
}

/// int扩展
extension IntX on int {
  Duration get milliseconds => Duration(milliseconds: this);
  Duration get seconds => Duration(seconds: this);
  Duration get minutes => Duration(minutes: this);
  Duration get hours => Duration(hours: this);
  
  /// 生成范围列表
  List<int> get range => List.generate(this, (i) => i);
}

/// List扩展
extension ListX<T> on List<T> {
  /// 安全取值
  T? getOrNull(int index) {
    if (index >= 0 && index < length) return this[index];
    return null;
  }
  
  /// 分块
  List<List<T>> chunk(int size) {
    var chunks = <List<T>>[];
    for (var i = 0; i < length; i += size) {
      chunks.add(sublist(i, i + size > length ? length : i + size));
    }
    return chunks;
  }
  
  /// 去重(保持顺序)
  List<T> distinct() {
    var seen = <T>{};
    return where((item) => seen.add(item)).toList();
  }
}

void extensionDemo() {
  // String扩展
  print('hello world'.capitalized);   // Hello world
  print('camelCaseName'.snakeCase);   // camel_case_name
  print('user@example.com'.isValidEmail); // true
  print('这是一段很长的文字需要截断'.truncate(8)); // 这是一段很...
  
  // int扩展
  var delay = 3.seconds;
  print('延迟: $delay'); // 0:00:03.000000
  
  // List扩展
  var numbers = [1, 2, 3, 4, 5, 6, 7];
  print(numbers.chunk(3));      // [[1, 2, 3], [4, 5, 6], [7]]
  print(numbers.getOrNull(10)); // null
  
  var withDuplicates = [3, 1, 2, 3, 2, 4];
  print(withDuplicates.distinct()); // [3, 1, 2, 4]
}

📖 密封类(Sealed Class)

// ✅ 验证通过:Dart 3.0+
/// 密封类——限制子类,编译器可穷举检查
sealed class Result<T> {
  const Result();
}

class Success<T> extends Result<T> {
  final T data;
  const Success(this.data);
}

class Failure<T> extends Result<T> {
  final String message;
  final int? code;
  const Failure(this.message, {this.code});
}

class Loading<T> extends Result<T> {
  const Loading();
}

// 使用——switch必须穷举所有子类
String describeResult(Result<int> result) {
  return switch (result) {
    Success(:final data) => '成功: $data',
    Failure(:final message, :final code) => '失败: $message (code: $code)',
    Loading() => '加载中...',
  };
}

// 网络请求结果
sealed class NetworkState {}
class NetworkIdle extends NetworkState {}
class NetworkLoading extends NetworkState {
  final double progress;
  NetworkLoading(this.progress);
}
class NetworkSuccess<T> extends NetworkState {
  final T data;
  NetworkSuccess(this.data);
}
class NetworkError extends NetworkState {
  final String message;
  final int statusCode;
  NetworkError(this.message, this.statusCode);
}

💻 实战:任务管理系统

// ✅ 验证通过:Dart 3.4
// 文件:lib/task_manager.dart

/// 综合运用:类、继承、Mixin、密封类、扩展方法

// 扩展方法
extension DateTimeX on DateTime {
  String get formatted => '$year-${month.toString().padLeft(2, '0')}-${day.toString().padLeft(2, '0')}';
}

// 优先级枚举
enum Priority { low, medium, high, urgent }

extension PriorityX on Priority {
  String get label => switch (this) {
    Priority.low => '低',
    Priority.medium => '中',
    Priority.high => '高',
    Priority.urgent => '紧急',
  };
  
  String get emoji => switch (this) {
    Priority.low => '🟢',
    Priority.medium => '🟡',
    Priority.high => '🟠',
    Priority.urgent => '🔴',
  };
}

// 任务状态密封类
sealed class TaskStatus {
  const TaskStatus();
}

class Todo extends TaskStatus {
  const Todo();
}

class InProgress extends TaskStatus {
  final DateTime startedAt;
  const InProgress(this.startedAt);
}

class Completed extends TaskStatus {
  final DateTime completedAt;
  const Completed(this.completedAt);
}

class Cancelled extends TaskStatus {
  final String reason;
  const Cancelled(this.reason);
}

// Loggable Mixin
mixin Loggable {
  void log(String message) {
    print('[${DateTime.now().formatted}] $message');
  }
}

// 基础任务类
class Task with Loggable {
  final String id;
  final String title;
  final String description;
  final Priority priority;
  final DateTime createdAt;
  final String? assignee;
  final List<String> tags;
  TaskStatus _status;
  
  Task({
    required this.id,
    required this.title,
    required this.description,
    this.priority = Priority.medium,
    DateTime? createdAt,
    this.assignee,
    List<String>? tags,
    TaskStatus? status,
  })  : createdAt = createdAt ?? DateTime.now(),
        tags = tags ?? [],
        _status = status ?? const Todo();
  
  TaskStatus get status => _status;
  
  void start() {
    if (_status is! Todo) {
      log('无法启动: 当前状态不是待办');
      return;
    }
    _status = InProgress(DateTime.now());
    log('任务启动: $title');
  }
  
  void complete() {
    if (_status is! InProgress) {
      log('无法完成: 当前状态不是进行中');
      return;
    }
    _status = Completed(DateTime.now());
    log('任务完成: $title');
  }
  
  void cancel(String reason) {
    if (_status is Completed) {
      log('无法取消: 已完成的任务');
      return;
    }
    _status = Cancelled(reason);
    log('任务取消: $title ($reason)');
  }
  
  String statusLabel() => switch (_status) {
    Todo() => '📋 待办',
    InProgress(:final startedAt) => '🔄 进行中 (开始: ${startedAt.formatted})',
    Completed(:final completedAt) => '✅ 完成 (${completedAt.formatted})',
    Cancelled(:final reason) => '❌ 已取消 ($reason)',
  };
  
  @override
  String toString() => '${priority.emoji} $title [${statusLabel()}]';
}

// 任务管理器
class TaskManager with Loggable {
  final List<Task> _tasks = [];
  int _nextId = 1;
  
  Task createTask({
    required String title,
    String description = '',
    Priority priority = Priority.medium,
    String? assignee,
    List<String>? tags,
  }) {
    var task = Task(
      id: 'T${_nextId++}',
      title: title,
      description: description,
      priority: priority,
      assignee: assignee,
      tags: tags,
    );
    _tasks.add(task);
    log('创建任务: ${task.id} - $title');
    return task;
  }
  
  List<Task> get allTasks => List.unmodifiable(_tasks);
  
  List<Task> byStatus(Type statusType) =>
      _tasks.where((t) => t.status.runtimeType == statusType).toList();
  
  List<Task> byPriority(Priority p) =>
      _tasks.where((t) => t.priority == p).toList();
  
  List<Task> search(String query) =>
      _tasks.where((t) => t.title.contains(query) || t.description.contains(query)).toList();
  
  Map<Priority, List<Task>> groupByPriority() {
    var groups = <Priority, List<Task>>{};
    for (var task in _tasks) {
      groups.putIfAbsent(task.priority, () => []).add(task);
    }
    return groups;
  }
  
  String summary() {
    var todo = byStatus(Todo).length;
    var inProgress = byStatus(InProgress).length;
    var completed = byStatus(Completed).length;
    var cancelled = byStatus(Cancelled).length;
    return '任务统计: 📋$todo 🔄$inProgress ✅$completed ❌$cancelled / 共${_tasks.length}';
  }
}

void main() {
  var manager = TaskManager();
  
  var task1 = manager.createTask(
    title: '完成首页UI',
    description: '实现Material Design风格的首页布局',
    priority: Priority.high,
    assignee: 'Alice',
    tags: ['UI', '首页'],
  );
  
  var task2 = manager.createTask(
    title: '集成登录API',
    priority: Priority.urgent,
    tags: ['API', '认证'],
  );
  
  var task3 = manager.createTask(
    title: '编写单元测试',
    priority: Priority.low,
  );
  
  // 操作任务
  task1.start();
  task1.complete();
  task2.start();
  task3.cancel('需求变更');
  
  // 查询
  print('\n--- 所有任务 ---');
  for (var task in manager.allTasks) {
    print(task);
  }
  
  print('\n--- 按优先级分组 ---');
  for (var entry in manager.groupByPriority().entries) {
    print('${entry.key.emoji} ${entry.key.label}: ${entry.value.length}个任务');
  }
  
  print('\n${manager.summary()}');
}
[2026-05-19] 创建任务: T1 - 完成首页UI [2026-05-19] 创建任务: T2 - 集成登录API [2026-05-19] 创建任务: T3 - 编写单元测试 [2026-05-19] 任务启动: 完成首页UI [2026-05-19] 任务完成: 完成首页UI [2026-05-19] 任务启动: 集成登录API [2026-05-19] 任务取消: 编写单元测试 (需求变更) --- 所有任务 --- 🟠 完成首页UI [✅ 完成 (2026-05-19)] 🔴 集成登录API [🔄 进行中 (开始: 2026-05-19)] 🟢 编写单元测试 [❌ 已取消 (需求变更)] --- 按优先级分组 --- 🟠 高: 1个任务 🔴 紧急: 1个任务 🟢 低: 1个任务 任务统计: 📋0 🔄1 ✅1 ❌1 / 共3

🏋️ 练习

练习1:银行账户类 🏦

实现一个银行账户类:

练习2:图形层次结构 📐

扩展Shape类体系,添加:

练习3:扩展方法库 📚

为以下类型编写实用扩展方法:

🏆 本课成就

📚 下节预告

下一课:异步编程——Future、async/await、Stream和Isolate,Dart的并发模型。