Immutability
不可变性,在代码中,我们通常使用final 声明一个值被声明之后不能被改变,也就是只有get
方法,没有set
方法。这在很多场景下很有用,但这也不是银弹
。我们需要了解不可变性的适用范围,否则freezed的滥用也会导致我们的代码变得冗余
和晦涩
。
不可变性是一种设计模式,他关注的一个对象被创建后不可修改(包括内部的变量),这点很类似于Rust
的Copy
语意,也就是值拷贝,对对象的修改,是通过产生新的对象,保持,也类似于集合当中的CopyOnWirte
, 他们都保证了数据一致性
。
不可变性和Bloc
, 或者说,一种状态管理
模式很搭配,当我们使用一个Class而不是基础数据结构做状态时,我们必须重写 hashCode
和 ==
来保证当内部状态被改变时,产生了状态不一致,进而需要通知页面修改最新的状态。这并不是一个容易的过程,但优势是显而易见的。
- 易于推理和测试: 状态的修改是可以在类的级别进行记录,而不需要对没有属性进行记录。且对象不会在运行时改变,对测试更友好。
- 无副作用函数式编程: 在非纯函数编程的语言中,我们都会写出产生副作用的代码,对象的不可变性,避免了这种副作用的产生。
常见的正确用法
数据传输对象(DTO)
当你需要定义用于数据传输的简单数据类时,使用 freezed
可以方便地创建不可变的 DTO,确保数据的一致性和不变性。
state 状态管理
- 单一职责,状态的定义清晰,
- 状态改变将产生新的状态,状态转移可控。
dart
@freezed
class SignInState with _$SignInState {
const factory SignInState({
String? email,
String? password,
required bool isSubmitting,
required Option<String> passwordError,
required Option<String> emailError,
required Option<Either<UserProfile, PixError>> successOrFail,
}) = _SignInState;
factory SignInState.initial() => SignInState(
isSubmitting: false,
passwordError: none(),
emailError: none(),
successOrFail: none(),
);
}
事件定义
- 不可变性也非常适合,Bloc中事件的定义,一旦事件被产生,中途不可被修改,不必担心事件中途被修改
dart
@freezed
class SignInEvent with _$SignInEvent {
const factory SignInEvent.signedInWithUserEmailAndPassword() =
SignedInWithUserEmailAndPassword;
const factory SignInEvent.emailChanged(String email) = EmailChanged;
const factory SignInEvent.passwordChanged(String password) = PasswordChanged;
}
不太友好的使用
在DDD中领域Model中,我们需要对模型对象的值修改,或者执行某些操作需要改变内部值,但不可变性的特质使我们无法在内部完成这件事,我们必须将内部变化的逻辑放到外部,降低了内聚性。
dart
@freezed
@HiveType(typeId: 4)
class EffectiveRange with _$EffectiveRange {
factory EffectiveRange({
@HiveField(0) required int start,
@HiveField(1) required int end,
}) = _EffectiveRange;
}
由于内部并不能添加修改相关的方法,无法继承Hive Object
,进行操作。我们也无法提供其他方法优化对Model
的访问。但在dart中有一种读取的思路是使用 extension
dart
extension EffectiveRangeExtension on EffectiveRange {
String get labelStart => start.kTimeLabel;
String get labelEnd => end.kTimeLabel;
String get label => "$labelStart -$labelEnd";
}
不可变性
和freezed
的关系是,理论和实现方式的,不可变性在实际项目使用当中要进行一定的权衡,它并不是 银弹
。但我们可以通过freezed 生成的方法,改变为部分可变性,这在某些场景下也非常的合适。使用的方式和范围要看自己实际的项目需求了。
Trade Off
需要实际使用过两种甚至多种方式,并发现优势和不足,才能做的,多做,多写,多深入,才能做好这份工作。