1.6 类与对象

基础知识

面向对象编程(OOP)是 Dart 的核心特性之一。类是创建对象的蓝图或模板,而对象是类的实例。通过类和对象,我们可以将数据(属性)和行为(方法)封装在一起,从而更好地组织和管理代码。

1. 类的定义

在 Dart 中,使用 class 关键字来定义类。类可以包含属性(实例变量)和方法(函数)。

dart
复制代码
class Person {
  // 属性(实例变量)
  String name; // 姓名
  int age;     // 年龄

  // 构造函数
  // 默认构造函数,如果未显式定义,Dart 会提供一个无参的默认构造函数
  // Person(this.name, this.age); // 简写形式

  // 显式定义构造函数
  Person(String name, int age) {
    this.name = name;
    this.age = age;
  }

  // 方法
  void greet() {
    print("Hello, my name is $name and I am $age years old.");
  }

  // Getter 和 Setter (可选,Dart 会自动生成)
  // String get getName => name;
  // set setAge(int newAge) => age = newAge;
}

2. 对象的创建

使用 new 关键字(可选)和类的构造函数来创建对象。Dart 3 之后,new 关键字可以省略。

dart
复制代码
void main() {
  // 创建 Person 类的对象
  var person1 = Person("Alice", 30); // 推荐写法
  // Person person2 = new Person("Bob", 25); // 旧写法,new 关键字可省略

  // 访问对象的属性
  print(person1.name); // 输出: Alice
  print(person1.age);  // 输出: 30

  // 调用对象的方法
  person1.greet(); // 输出: Hello, my name is Alice and I am 30 years old.

  // 修改对象的属性
  person1.age = 31;
  person1.greet(); // 输出: Hello, my name is Alice and I am 31 years old.
}

3. 构造函数

Dart 支持多种类型的构造函数:

  • 默认构造函数:如果没有定义任何构造函数,Dart 会提供一个无参的默认构造函数。
  • 命名构造函数:允许一个类有多个构造函数,通过不同的名称来区分。这在需要以不同方式创建对象时非常有用。
  • 工厂构造函数:使用 factory 关键字定义。它不总是创建类的新实例,可以返回缓存的实例或子类的实例。工厂构造函数不能访问 this
  • 常量构造函数:使用 const 关键字定义。如果一个类只包含 final 字段,并且有一个 const 构造函数,那么可以创建编译时常量对象。

示例:命名构造函数和工厂构造函数

dart
复制代码
class Point {
  final double x;
  final double y;

  // 默认构造函数
  Point(this.x, this.y);

  // 命名构造函数:从原点创建点
  Point.origin() : this(0.0, 0.0);

  // 命名构造函数:从数组创建点
  Point.fromList(List<double> coords) : this(coords[0], coords[1]);

  // 工厂构造函数:返回缓存的实例
  static final Map<String, Point> _cache = <String, Point>{};
  factory Point.fromJson(Map<String, double> json) {
    final key = "${json["x"]},${json["y"]}";
    if (_cache.containsKey(key)) {
      return _cache[key]!;
    } else {
      final newPoint = Point(json["x"]!, json["y"]!);
      _cache[key] = newPoint;
      return newPoint;
    }
  }

  // 常量构造函数 (如果所有字段都是 final)
  const Point.constant(this.x, this.y);
}

void main() {
  var p1 = Point(10.0, 20.0);
  var p2 = Point.origin(); // 使用命名构造函数
  var p3 = Point.fromList([5.0, 5.0]); // 使用命名构造函数

  print("p1: (${p1.x}, ${p1.y})"); // 输出: p1: (10.0, 20.0)
  print("p2: (${p2.x}, ${p2.y})"); // 输出: p2: (0.0, 0.0)
  print("p3: (${p3.x}, ${p3.y})"); // 输出: p3: (5.0, 5.0)

  var jsonPoint1 = Point.fromJson({"x": 1.0, "y": 2.0});
  var jsonPoint2 = Point.fromJson({"x": 1.0, "y": 2.0});
  print(jsonPoint1 == jsonPoint2); // 输出: true (因为返回的是同一个缓存实例)

  const Point constPoint = Point.constant(1.0, 1.0);
}

4. 初始化列表

构造函数除了可以有参数列表和函数体外,还可以有一个初始化列表。初始化列表在构造函数体执行之前运行,常用于初始化 final 字段或在构造函数体执行前进行一些断言。

dart
复制代码
class Rectangle {
  final double width;
  final double height;

  Rectangle(this.width, this.height) : assert(width >= 0 && height >= 0, 'Width and height must be non-negative');

  double get area => width * height;
}

void main() {
  var rect = Rectangle(10.0, 5.0);
  print("Area: ${rect.area}"); // 输出: Area: 50.0

  // var invalidRect = Rectangle(-1.0, 5.0); // 会抛出 AssertionError
}

官方文档链接

Flutter 开发中的应用案例

在 Flutter 中,几乎所有的 UI 元素都是 Widget,而 Widget 本质上就是 Dart 的类。理解类和对象的概念是掌握 Flutter UI 构建和状态管理的基础。Flutter 的声明式 UI 范式大量依赖于 Widget 类的组合和属性传递。

案例:自定义用户头像组件

我们将创建一个自定义的用户头像组件 UserAvatar,它是一个 StatelessWidget,用于展示用户的头像图片和姓名。这个案例将演示类的定义、属性、构造函数以及如何在 Flutter 中组合 Widget。

dart
复制代码
import 'package:flutter/material.dart';

// 定义一个表示用户数据的类
class User {
  final String name;
  final String avatarUrl;

  // 构造函数,使用命名参数更清晰
  const User({required this.name, required this.avatarUrl});
}

// 自定义用户头像组件,它是一个 StatelessWidget
class UserAvatar extends StatelessWidget {
  final User user; // 接收一个 User 对象作为属性
  final double radius; // 头像半径

  // 构造函数,使用 required 关键字确保 user 和 radius 必须被提供
  const UserAvatar({
    super.key,
    required this.user,
    this.radius = 30.0, // 提供默认值
  });

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        CircleAvatar(
          radius: radius,
          backgroundImage: NetworkImage(user.avatarUrl), // 使用用户头像 URL
          backgroundColor: Colors.grey[200],
          child: user.avatarUrl.isEmpty
              ? Icon(Icons.person, size: radius) // 如果没有头像 URL,显示默认图标
              : null,
        ),
        const SizedBox(height: 8.0),
        Text(
          user.name, // 显示用户姓名
          style: const TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold),
        ),
      ],
    );
  }
}

class ClassAndObjectExampleScreen extends StatelessWidget {
  const ClassAndObjectExampleScreen({super.key});

  @override
  Widget build(BuildContext context) {
    // 创建 User 类的对象
    final User user1 = User(
      name: '张三',
      avatarUrl: 'https://avatars.githubusercontent.com/u/1?v=4', // 示例头像 URL
    );

    final User user2 = User(
      name: '李四',
      avatarUrl: 'https://avatars.githubusercontent.com/u/2?v=4', // 示例头像 URL
    );

    final User user3 = User(
      name: '王五',
      avatarUrl: '', // 没有头像 URL 的用户
    );

    return Scaffold(
      appBar: AppBar(
        title: const Text('类与对象应用案例'),
      ),
      body: Center(
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: <Widget>[
            UserAvatar(user: user1), // 传递 User 对象给 UserAvatar 组件
            UserAvatar(user: user2, radius: 40.0), // 传递 User 对象并指定半径
            UserAvatar(user: user3), // 传递没有头像 URL 的用户
          ],
        ),
      ),
    );
  }
}

void main() {
  runApp(const MaterialApp(home: ClassAndObjectExampleScreen()));
}

案例分析:

  • class User:我们定义了一个 User 类,它有两个 final 属性 nameavatarUrlfinal 关键字表示这些属性在对象创建后不能被修改,这符合 Flutter 中不可变 Widget 的设计理念。
  • const User({required this.name, required this.avatarUrl});User 类的构造函数使用了命名参数和 required 关键字。命名参数使得创建 User 对象时代码更具可读性,required 确保了这些参数在创建对象时必须被提供。
  • class UserAvatar extends StatelessWidgetUserAvatar 是一个自定义的 Flutter Widget,它继承自 StatelessWidget。这意味着 UserAvatar 的状态在创建后不会改变。它接收一个 User 对象和一个 radius 作为属性。
  • final User user;UserAvatar 组件将 User 对象作为其属性之一。这展示了如何在 Flutter 中通过类的属性来传递数据。
  • const UserAvatar({super.key, required this.user, this.radius = 30.0,});UserAvatar 的构造函数也使用了命名参数和 required 关键字。radius 参数还提供了默认值 30.0,使得在创建 UserAvatar 时可以省略 radius 参数。
  • NetworkImage(user.avatarUrl):在 UserAvatarbuild 方法中,我们使用 user.avatarUrl 来加载网络图片。这直接使用了 User 对象的属性。
  • UserAvatar(user: user1):在 ClassAndObjectExampleScreen 中,我们创建了 User 类的实例 user1user2user3,并将这些对象作为参数传递给 UserAvatar 组件。这体现了面向对象编程中对象作为参数传递的特性。

这个案例清晰地展示了 Dart 的类和对象在 Flutter 开发中的核心作用。通过定义和组合类,我们可以构建出模块化、可复用且易于管理的用户界面组件。理解如何有效地使用类和对象是编写高质量 Flutter 应用的关键。