Объяснение концепции шаблона состояния во Flutter
Шаблон состояния служит паттерном проектирования поведения, который помогает инкапсулировать различное поведение объекта в соответствии с его внутренним состоянием. Это позволяет объекту динамически изменять свое поведение, не полагаясь на условные операторы, что в конечном итоге повышает удобство обслуживания.
Раскрытие сущности шаблона состояний
Согласно ресурсу «Шаблоны проектирования в Dart», шаблон состояния действует как паттерн поведенческого проектирования, играющий ключевую роль в инкапсуляции разнообразного поведения объекта на основе его внутреннего состояния. При таком подходе объект может динамически корректировать свое поведение без необходимости использования условных операторов, тем самым упрощая базу кода.
По сути, каждое состояние объекта представляется отдельными классами, служащими расширениями или вариациями основного класса состояния, специфичного для этого объекта.
Углубление концепции
Рассмотрите возможность создания объекта типа Water
, где вы определяете WaterState
для воплощения таких вариантов, как Solid
, Liquid
и Gaseous
:
abstract class WaterState {
//
}
class Solid extends WaterState {
//
}
class Liquid extends WaterState {
//
}
class Gaseous extends WaterState {
//
}
Более того, шаблон состояния становится жизненно важным компонентом шаблона BLoC. Если вы используете шаблон BLoC, вы, по сути, используете паттерн состояния.
Идеальные сценарии реализации шаблона состояний
State Pattern пригодится в следующих случаях:
- Когда объект ведет себя по-разному в зависимости от его состояния
- Если число состояний существенно и изменения состояний происходят часто
- Когда класс содержит множество условий, влияющих на его поведение, связанное со значениями полей
- В случаях, когда имеется значительное дублирование кода для аналогичных состояний и переходов
Применение паттерна состояния в реальной жизни
Давайте теперь рассмотрим практический пример, демонстрирующий шаблон состояния в действии. Рассмотрим приведенный ниже класс, где определения состояний включают переменные и ChangeNotifier
:
class CategoryStore extends ChangeNotifier {
List<String> categories = [];
bool isLoading = false;
String error = '';
IApiDatasource apiDatasource = ApiDatasource();
void getCategories() async {
isLoading = true;
await apiDatasource.getCategories().then((response) {
response.fold(
(left) => error = left.message,
(right) => categories = right,
);
});
isLoading = false;
notifyListeners();
}
}
Выделенный выше метод fold()
связан с типом Either
— важным элементом функционального программирования, который представляет значение, подпадающее под один из двух указанных типов. Обычно либо представляет успешное, либо неудавшееся значение, как в примере, где left
означает значение ошибки и right
означает значение успеха.
Внутри вышеупомянутого класса функция инициирует загрузку через isLoading
, затем переходит к получению данных из API и сохранению их в переменной categories
. По завершении загрузки функция меняет значение на false
.
Чтобы упростить этот код с помощью шаблона состояния, мы структурируем его следующим образом:
Определение классов состояний
Состояния и соответствующие им классы объявляются изначально:
abstract class CategoryState {}
class CategoryInitial extends CategoryState {}
class CategoryLoading extends CategoryState {}
class CategoryLoaded extends CategoryState {
final List<String> categories;
CategoryLoaded(this.categories);
}
class CategoryError extends CategoryState {
final String message;
CategoryError(this.message);
}
Уточнение класса Store
В функции getCategories()
мы удаляем переменную isLoading
и вводим value
переменную, представляющую состояние внутри ValueNotifier
и соответствующую CategoryState
. Следовательно, CategoryLoading
предназначен для обозначения нашего состояния.
Впоследствии в fold()
методе, если запрос не удается, value
переходит в состояние Error
, несущее сообщение об ошибке (left
). И наоборот, в случае успешного результата он принимает состояние Loaded
с данными, находящимися в переменной right
. Этот рабочий процесс отбрасывает переменные categories
и error
:
class CategoryStore extends ValueNotifier<CategoryState> {
CategoryStore() : super(CategoryInitial());
IApiDatasource apiDatasource = ApiDatasource();
void getCategories() async {
value = CategoryLoading();
await apiDatasource.getCategories().then((response) {
response.fold(
(left) => value = CategoryError(left.message),
(right) => value = CategoryLoaded(right),
);
});
}
}
Геттер value
относится к ValueNotifier
, хранящему текущее состояние нашего класса Store
.
Демонстрация экземпляра на странице
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
// CategoryStore instance establishment
CategoryStore store = CategoryStore();
// Initialization function upon page load
@override
void initState() {
store.getCategories();
super.initState();
}
@override
void dispose() {
super.dispose();
store.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Expanded(
// Observing the store
child: ValueListenableBuilder(
valueListenable: store,
builder: (context, value, child) {
// Error handling
if (value is CategoryError) {
return Center(
child: Text(
'Error loading categories: ${value.message}',
),
);
}
// Presenting loaded data
if (value is CategoryLoaded) {
return ListView.builder(
itemCount: value.categories.length,
itemBuilder: (context, index) {
final category = value.categories[index];
return Text(category);
},
);
}
// Display loading status
return const Center(child: CircularProgressIndicator());
},
),
),
);
}
}
Таким образом, мы успешно реализовали шаблон состояния! Благодаря этому подходу мы придерживаемся принципов единой ответственности и открытости/закрытости, а также оптимизируем код, устраняя условные операторы, которые в противном случае могли бы усложнить кодовую базу.
Подведение итогов
Мы очень ценим ваше путешествие по этому исследованию State Pattern. Хотя основное внимание уделялось самому шаблону состояний, мы воздержались от слишком глубокого вникания в такие элементы, как ValueNotifier
и другие, использованные в примере.