Проверка замороженных классов модели
Я ищу хороший способ проверить замороженные модели. Пока что я придумал три подхода, которые показаны во фрагменте ниже.
@freezed
class Options with _$Options {
Options._();
factory Options._internal({required List<String> languages}) = _Options;
// #1: validation in factory constructor
factory Options({required List<String> languages}) {
if (languages.isEmpty) {
throw Exception('There must be at least one language.');
}
return Options._internal(languages: languages);
}
// #2: expose mutation methods with built-in validation
Options changeLanguages(List<String> languages) {
if (languages.isEmpty) {
throw Exception('There must be at least one language.');
}
return copyWith(languages: languages);
}
// #3: validation using custom properties
late final List<Exception> validationResult = <Exception>[
if (languages.isEmpty) Exception('There must be at least one language.'),
];
// #4: validation using a custom method
void validate() {
if (languages.isEmpty) {
throw Exception('There must be at least one language.');
}
}
}
№1: Проверка внутри конструктора фабрики. К сожалению, это работает только для вновь созданных объектов и требует дальнейших изменений для.
# 2: Проверка внутри метода мутации. Это можно было бы использовать в дополнение к # 1 для запуска проверки после создания объекта, но все равно не работает для
copyWith
.
№3: Отображение свойства с ошибками проверки. Пока что это мой любимый подход, хотя он требует от пользователей модели явного поиска ошибок.
№4: Вариант №3 , в котором вместо списка ошибок используется метод выброса.
Что вы думаете об этом? Знаете ли вы какие-нибудь лучшие подходы или есть часть API пакета, которую я упустил?
3 ответа
Чтобы сделать недопустимое состояние непредставимым, вам нужна проверка конструктора. Если класс является изменчивым, вам, конечно, также необходимо проверить его при изменении. Ни одно из приведенных выше решений не кажется таким уж полезным. Лучшее решение, которое у меня есть, — не использовать Freezed. Freezed — отличный пакет, который делает действительно много. Но во многих случаях это может быть YAGNI. Итак, если вам не нужны все функции, принесите пакеты, которые предоставляют только те, которые вам действительно нужны, и вручную сверните те, для которых нет пакета.
См. это обсуждение с Реми: https://github.com/rrousselGit/freezed/issues/830 .
Заморожена добавленная поддержка пользовательских s в v0.12.0: https://pub.dev/packages/freezed#asserts. Применение их к вашему примеру приводит к следующему:
@freezed
abstract class Options with _$Options {
Options._();
@Assert('languages.isNotEmpty', 'There must be at least one language.')
factory Options({required List<String> languages}) = _Options;
}
Однако это не позволяет вам генерировать произвольные исключения и
assert
s включены только в отладочные сборки, но не в сборки профиля / выпуска.
Я бы, наверное, переместил валидацию на шаг ниже. Вы можете создать модель
LanguageList
:
class LanguageList {
LanguageList(this.data) {
if (data.isEmpty) throw ArgumentError('Provide at least one element');
}
final List<String> data;
@override
bool operator ==(Object other) => other is LanguageList && ListEquality<String>.equals(data, other.data);
@override
int get hashCode => DeepCollectionEquality().hash(data);
}
и использовать его в
Options
модель:
factory Options._internal({required LanguageList languages}) = _Options;
Вы даже можете сделать его более «дружественным к компиляции», сделав недопустимые состояния непредставимыми вместо того, чтобы выдавать ошибку во время выполнения:
class LanguageList {
LanguageList(String head, Iterable<String> tail) : data = [head, ...tail];
final List<String> data;
@override
bool operator ==(Object other) => other is LanguageList && ListEquality<String>.equals(data, other.data);
@override
int get hashCode => DeepCollectionEquality().hash(data);
}
В этом случае просто невозможно создать неправильный экземпляр.