[译]Flutter用于对象比较的库Equatable

1,444 阅读4分钟

「这是我参与2022首次更文挑战的第11天,活动详情查看:2022首次更文挑战」。

本文翻译自pub: equatable | Dart Package (flutter-io.cn)

译时版本: equatable: 2.0.3


概览

在 Dart 中使比较对象可用经常会涉及到覆写 == 操作符和 hashCode

这样不只是啰嗦冗长,比较失败的话,还会导致代码性能低下,出现期望之外的行为。

默认情况下,如果两个对象是同一个实例,则 == 返回 true 。

比方说有下面这样的类:

class Person {
  const Person(this.name);

  final String name;
}

可以如下创建实例:

void main() {
  final Person bob = Person("Bob");
}

之后试着比较 Person 的两个实例,不管在是生产环境代码中,还是在测试代码中,都会出现问题:

print(bob == Person("Bob")); // false

关于这一点的更多信息,可参考官方 Dart 文档

为了能比较 Person 的两个实例,我们需要修改类,覆写 == 和 hashCode 如下:

class Person {
  const Person(this.name);

  final String name;

  @override
  bool operator ==(Object other) =>
    identical(this, other) ||
    other is Person &&
    runtimeType == other.runtimeType &&
    name == other.name;

  @override
  int get hashCode => name.hashCode;
}

现在如果再次运行以下代码:

print(bob == Person("Bob")); // true

它就能够对 Person 的不同实例进行比较了。

可以看到当处理复杂的类时,这会如何快速地变得很麻烦。这就是 Equatable 的用武之地!

Equatable 做什么?

Equatable 已为你覆写 == 和 hashCode ,所以你不需要浪费时间写大量的样板代码。

也有其它的包确实会为你生成样板代码,尽管这样,你仍然需要运行代码生成的步骤,这并不是理想的。

使用 Equatable , 不会有代码生成,我们可以更多地聚焦于写惊艳的应用和更少地世俗工作。

使用

首先,我们需要在 pubspec.yaml 文件中添加 equatable 依赖:

dependencies:
  equatable: ^2.0.0

接下来,我们需要安装它:

# Dart
pub get

# Flutter
flutter packages get

最后,我们需要继承 Equatable

import 'package:equatable/equatable.dart';

class Person extends Equatable {
  const Person(this.name);

  final String name;

  @override
  List<Object> get props => [name];
}

同时使用 json :

import 'package:equatable/equatable.dart';

class Person extends Equatable {
  const Person(this.name);

  final String name;

  @override
  List<Object> get props => [name];

  factory Person.fromJson(Map<String, dynamic> json) {
    return Person(json['name']);
  }
}

我们现在可以只像以前一样比较 Person 的实例,没有写所有样板代码的痛苦。

注意: Equatable 设计成只用于不可改变的对象,所以所有的成员必须是 final (这不只是 Equatable 的特性 - 使用可改变的值覆写 hashCode 会破坏基于 hash 的集合)。

Equatable 也支持 const 构造方法:

import 'package:equatable/equatable.dart';

class Person extends Equatable {
  const Person(this.name);

  final String name;

  @override
  List<Object> get props => [name];
}

toString 实现

Equatable 可以实现含有所有赋予的 props 的 toString 方法。如果想要为指定的 Equatable 对象这样处理,只需要包含如下代码:

@override
bool get stringify => true;

例:

import 'package:equatable/equatable.dart';

class Person extends Equatable {
  const Person(this.name);

  final String name;

  @override
  List<Object> get props => [name];

  @override
  bool get stringify => true;
}

对于 name Bob , 输出会如下:

Person(Bob)

这个标志默认是 false , toString 只会返回类型:

Person

EquatableConfig

stringify 可以通过 EquatableConfig 为所有的 Equatable 实例进行全局配置:

EquatableConfig.stringify = true;

如果 stringify 被指定的 Equatable 类覆写,则 EquatableConfig.stringify 的值会被忽略。换句话说,本地的配置优先于全局的配置。

注意: EquatableConfig.stringify 在调试模式默认为 true ,在发布模式默认为 false 。

扼要重述

不使用 Equatable

class Person {
  const Person(this.name);

  final String name;

  @override
  bool operator ==(Object other) =>
    identical(this, other) ||
    other is Person &&
    runtimeType == other.runtimeType &&
    name == other.name;

  @override
  int get hashCode => name.hashCode;
}

使用 Equatable

import 'package:equatable/equatable.dart';

class Person extends Equatable {
  const Person(this.name);

  final String name;

  @override
  List<Object> get props => [name];
}

EquatableMixin

有的时候,由于类已经继承了一个父类,所以不能再继承 Equatable 。这种情况下, 你仍然可以通过使用 EquatableMixin 来获得 Equatable 的便利:

使用

比方说我们想创建一个 EquatableDateTime 类,我们可以如下使用 EquatableMixin

class EquatableDateTime extends DateTime with EquatableMixin {
  EquatableDateTime(
    int year, [
    int month = 1,
    int day = 1,
    int hour = 0,
    int minute = 0,
    int second = 0,
    int millisecond = 0,
    int microsecond = 0,
  ]) : super(year, month, day, hour, minute, second, millisecond, microsecond);

  @override
  List<Object> get props {
    return [year, month, day, hour, minute, second, millisecond, microsecond];
  }
}

现在我们想要创建 EquatableDateTime 的一个子类,我们只需要覆写 props

class EquatableDateTimeSubclass extends EquatableDateTime {
  final int century;

  EquatableDateTimeSubclass(
    this.century,
    int year,[
    int month = 1,
    int day = 1,
    int hour = 0,
    int minute = 0,
    int second = 0,
    int millisecond = 0,
    int microsecond = 0,
  ]) : super(year, month, day, hour, minute, second, millisecond, microsecond);

  @override
  List<Object> get props => super.props..addAll([century]);
}

性能

你可能想知道如果你使用了 Equatable ,性能会有何影响。

结果(平均运行超过10次)

相等比较 A == A

运行时间 (μs)
手动0.193
空的Equatable0.191
含有Equatable0.190

实例化 A()

运行时间 (μs)
手动0.165
空的Equatable0.181
含有Equatable0.182

*性能测试运行于:Dart VM version: 2.4.0

维护者