Шаблон проектирования – это многократно используемое решение часто возникающей проблемы при проектировании и разработке программного обеспечения.
Преимущества шаблонов проектирования:
- Возможность повторного использования: шаблоны проектирования инкапсулируют повторно используемые решения, позволяя разработчикам применять проверенные подходы к аналогичным проблемам проектирования. Это снижает потребность в повторном изобретении решений и способствует повторному использованию кода.
- Ремонтопригодность: шаблоны проектирования обеспечивают четкую и организованную структуру кода, что облегчает его понимание и поддержку. Они предоставляют общий язык и структуру для разработчиков, улучшая совместную работу и общение внутри команд.
- Масштабируемость. Шаблоны проектирования помогают создавать масштабируемые программные системы, предоставляя рекомендации по управлению сложностью и позволяя легко расширять или модифицировать существующий код без нарушения работы системы.
- Гибкость. Шаблоны проектирования позволяют разработчикам создавать гибкое программное обеспечение, способное адаптироваться к изменяющимся требованиям. Они разъединяют компоненты и способствуют слабой связи, упрощая модификацию или замену отдельных элементов системы, не затрагивая другие части.
- Производительность. Некоторые шаблоны проектирования ориентированы на оптимизацию производительности и использования ресурсов. Они предоставляют методы для эффективного создания объектов, кэширования, отложенной загрузки и других соображений, связанных с производительностью.
Давайте углубимся в некоторые из наиболее часто используемых шаблонов проектирования в среде Flutter и поймем, как они могут помочь нам создавать надежные и удобные в сопровождении приложения Flutter.
- Шаблон одиночного элемента:
Шаблон Singleton гарантирует, что в приложении есть только один экземпляр класса. Во Flutter этот шаблон обычно используется для управления глобальным состоянием или ресурсами, к которым необходимо получить доступ из нескольких частей приложения. Пакет Provider
, созданный поверх InheritedWidget, является отличным примером реализации шаблона Singleton во Flutter. Он позволяет эффективно управлять состоянием, предоставляя единственный экземпляр класса провайдера, к которому могут обращаться виджеты в дереве виджетов.
Вот пример того, как вы можете реализовать шаблон Singleton во Flutter для приложения со списком дел:
class TaskManager { static TaskManager _instance; factory TaskManager() { if (_instance == null) { _instance = TaskManager._(); } return _instance; } TaskManager._(); List<Task> tasks = []; void addTask(Task task) { tasks.add(task); } void removeTask(Task task) { tasks.remove(task); } } class Task { final String title; final String description; Task({required this.title, required this.description}); }
В приведенном выше примере TaskManager
реализован как одноэлементный класс, который управляет задачами в приложении списка дел. У класса есть закрытый конструктор для предотвращения прямого создания экземпляров вне класса. Конструктор factory
TaskManager()
проверяет, существует ли уже экземпляр TaskManager
, и если нет, создает новый экземпляр. Конструктор factory
гарантирует, что во всем приложении будет только один экземпляр TaskManager
.
Затем вы можете использовать TaskManager
для управления задачами в приложении списка дел. Например, чтобы добавить задачу, вы можете вызвать TaskManager().addTask(task)
:
Task newTask = Task(title: "Buy groceries", description: "Milk, eggs, bread"); TaskManager().addTask(newTask);
Используя шаблон Singleton, вы можете гарантировать, что в приложении будет только один экземпляр TaskManager
, что позволит вам последовательно управлять задачами в разных частях приложения.
2. Шаблон построителя:
Шаблон Builder отделяет построение сложных объектов от их представления, делая процесс более гибким и удобочитаемым. Во Flutter шаблон Builder часто используется для создания виджетов с плавным API. Конструкторы ListView.builder
и GridView.builder
являются примерами шаблона Builder в действии. Они позволяют разработчикам эффективно создавать списки или сетки, предоставляя функцию построителя, которая создает отдельные виджеты по запросу, когда пользователь прокручивает список или сетку.
Вот пример того, как вы можете реализовать шаблон Builder во Flutter для приложения со списком дел:
class Task { final String title; final String description; Task({required this.title, required this.description}); } class TaskGrid extends StatelessWidget { final List<Task> tasks; TaskGrid({required this.tasks}); @override Widget build(BuildContext context) { return GridView.builder( itemCount: tasks.length, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, ), itemBuilder: (context, index) { return buildTaskCard(tasks[index]); }, ); } Widget buildTaskCard(Task task) { return Card( child: Column( children: [ Text(task.title), Text(task.description), ], ), ); } }
В приведенном выше примере TaskGrid
— это виджет Flutter, который отображает сетку задач с использованием GridView.builder
. Он следует шаблону Builder, отделяя построение отдельных карточек задач (виджетов) от их представления в сетке.
Виджет GridView.builder
используется для построения сетки, а его параметр itemBuilder
принимает функцию построения, которая отвечает за построение отдельных карточек задач. Функция itemBuilder
вызывается для каждого элемента в списке tasks
, и ей передается текущий индекс. Внутри itemBuilder
мы вызываем функцию buildTaskCard
для создания виджета карты задач для этой конкретной задачи.
Функция buildTaskCard
принимает объект Task
и возвращает виджет Card
, содержащий заголовок и описание задачи. Эта функция представляет часть Builder шаблона Builder, инкапсулируя создание и настройку виджета карты задач.
Используя шаблон Builder с GridView.builder
, мы отделяем построение отдельных карточек задач от их представления в сетке, делая код более модульным и простым в обслуживании.
3. Заводской шаблон
Шаблон Factory — это порождающий шаблон, который предоставляет интерфейс для создания объектов, но позволяет подклассам решать, экземпляр какого класса создавать.
Вот более простой пример того, как вы можете реализовать шаблон Factory во Flutter для приложения со списком дел
enum TaskType { personal, work, shopping, } class Task { final String title; final String description; final TaskType type; Task({required this.title, required this.description, required this.type}); factory Task.createPersonal({required String title, required String description}) { return Task(title: title, description: description, type: TaskType.personal); } factory Task.createWork({required String title, required String description}) { return Task(title: title, description: description, type: TaskType.work); } factory Task.createShopping({required String title, required String description}) { return Task(title: title, description: description, type: TaskType.shopping); } }
у нас есть класс Task
, представляющий задачу в приложении списка дел. У класса есть конструктор, который принимает параметры title
, description
и type
.
У нас также есть три фабричных метода: createPersonal
, createWork
и createShopping
. Каждый фабричный метод создает и возвращает объект Task
с указанными параметрами и устанавливает соответствующий TaskType
.
Для создания экземпляров задач вы можете использовать фабричные методы следующим образом:
Task personalTask = Task.createPersonal(title: 'Personal Task', description: 'This is a personal task'); Task workTask = Task.createWork(title: 'Work Task', description: 'This is a work-related task'); Task shoppingTask = Task.createShopping(title: 'Shopping Task', description: 'This is a shopping task');
4. Шаблон наблюдателя
Шаблон Observer устанавливает зависимость «один ко многим» между объектами, поэтому, когда один объект меняет свое состояние, все его зависимые объекты уведомляются и обновляются автоматически. Во Flutter шаблон Observer обычно используется для управления состоянием и реактивностью. Такие библиотеки, как Provider
и Bloc
, реализуют шаблон Observer, позволяя виджетам подписываться на изменения в состоянии приложения и автоматически перестраиваться при необходимости.
Вот пример того, как вы можете реализовать шаблон Observer во Flutter для приложения списка дел:
import 'dart:async'; class Task { String title; bool completed; Task({required this.title, this.completed = false}); } class TaskManager { StreamController<Task> _taskController = StreamController<Task>.broadcast(); Stream<Task> get taskStream => _taskController.stream; void addTask(Task task) { // Perform any additional logic if needed _taskController.add(task); } void updateTaskCompletion(Task task, bool completed) { // Perform any additional logic if needed task.completed = completed; _taskController.add(task); } void dispose() { _taskController.close(); } } class TaskWidget extends StatefulWidget { final Task task; final TaskManager taskManager; TaskWidget({required this.task, required this.taskManager}); @override _TaskWidgetState createState() => _TaskWidgetState(); } class _TaskWidgetState extends State<TaskWidget> { @override void initState() { super.initState(); widget.taskManager.taskStream.listen((updatedTask) { if (updatedTask == widget.task) { setState(() { // Update the widget state based on the task changes }); } }); } @override Widget build(BuildContext context) { // Build the task widget return ListTile( title: Text(widget.task.title), trailing: Checkbox( value: widget.task.completed, onChanged: (value) { widget.taskManager.updateTaskCompletion(widget.task, value!); }, ), ); } @override void dispose() { widget.taskManager.dispose(); super.dispose(); } }
5. Шаблон декоратора
Паттерн Decorator позволяет динамически добавлять поведение к объекту, не изменяя его исходный класс. Во Flutter шаблон Decorator используется через концепцию миксинов. Миксины — это классы, которые предоставляют дополнительную функциональность другим классам, не требуя наследования. Их можно использовать для добавления таких функций, как анимация, распознавание жестов или анализ данных, к существующим виджетам или классам, что делает их более гибкими и пригодными для повторного использования.
Вот пример того, как вы можете реализовать шаблон Decorator во Flutter для приложения списков дел с помощью миксинов:
abstract class Task { String get title; String get description; } class SimpleTask implements Task { @override final String title; @override final String description; SimpleTask({required this.title, required this.description}); } mixin Decorator implements Task { Task task; Decorator(this.task); @override String get title => task.title; @override String get description => task.description; } class PriorityTaskDecorator with Decorator { final String priority; PriorityTaskDecorator(Task task, this.priority) : super(task); @override String get title => '${task.title} [$priority]'; @override String get description => task.description; } class DueDateTaskDecorator with Decorator { final DateTime dueDate; DueDateTaskDecorator(Task task, this.dueDate) : super(task); @override String get title => task.title; @override String get description => '${task.description} - Due Date: $dueDate'; }
В приведенном выше примере у нас есть абстрактный класс Task
, представляющий задачу в приложении списка дел. Он имеет два свойства: title
и description
.
Класс SimpleTask
является конкретной реализацией интерфейса Task
. Представляет собой базовую задачу без каких-либо дополнительных украшений.
Миксин Decorator
используется для определения базовой функциональности декоратора. Он реализует интерфейс Task
и содержит ссылку на исходный объект задачи. Примесь переопределяет геттеры title
и description
для предоставления оформленных версий.
Классы PriorityTaskDecorator
и DueDateTaskDecorator
— это конкретные декораторы, расширяющие миксин Decorator
. Они добавляют дополнительные функции или изменяют поведение основной задачи. Например, PriorityTaskDecorator
добавляет метку приоритета к названию задачи, а DueDateTaskDecorator
добавляет к описанию задачи срок выполнения.
Вот пример того, как вы можете использовать декораторы:
void main() { Task task = SimpleTask(title: 'Buy groceries', description: 'Milk, eggs, bread'); Task priorityTask = PriorityTaskDecorator(task, 'High'); print(priorityTask.title); // Output: Buy groceries [High] print(priorityTask.description); // Output: Milk, eggs, bread Task dueDateTask = DueDateTaskDecorator(task, DateTime.now()); print(dueDateTask.title); // Output: Buy groceries print(dueDateTask.description); // Output: Milk, eggs, bread - Due Date: 2023-06-02 12:00:00.000 }
В приведенном выше примере мы создаем SimpleTask
, а затем применяем к нему декораторы, используя PriorityTaskDecorator
и DueDateTaskDecorator
. Каждый декоратор добавляет дополнительные функции или изменяет свойства задачи.
Вывод показывает, как декораторы влияют на title
и description
задачи в зависимости от их конкретных модификаций.
Используя шаблон Decorator с примесями, вы можете динамически добавлять или изменять поведение объектов во время выполнения без изменения их базовых классов. Это позволяет гибко и повторно использовать композицию объектов с различными комбинациями функций или украшений.
6. Шаблон стратегии:
Шаблон Strategy определяет семейство взаимозаменяемых алгоритмов, инкапсулируя каждый из них и делая их взаимозаменяемыми во время выполнения.
Вот пример того, как вы можете реализовать шаблон стратегии во Flutter для приложения со списком дел:
class Task { final String title; final DateTime dueDate; final int priority; Task({required this.title, required this.dueDate, required this.priority}); } typedef TaskSortingStrategy = List<Task> Function(List<Task> tasks); List<Task> sortByDueDate(List<Task> tasks) { tasks.sort((a, b) => a.dueDate.compareTo(b.dueDate)); return tasks; } List<Task> sortByPriority(List<Task> tasks) { tasks.sort((a, b) => a.priority.compareTo(b.priority)); return tasks; } class TaskManager { List<Task> _tasks = []; TaskSortingStrategy _sortingStrategy; TaskManager(this._sortingStrategy); void addTask(Task task) { _tasks.add(task); } void setSortingStrategy(TaskSortingStrategy strategy) { _sortingStrategy = strategy; } List<Task> getSortedTasks() { return _sortingStrategy(_tasks); } }
В этом упрощенном примере у нас есть класс Task
, представляющий задачу в приложении списка дел, с такими свойствами, как title
, dueDate
и priority
.
TaskSortingStrategy
— это тип функции, определяющий интерфейс стратегии для сортировки задач. Он принимает список задач в качестве входных данных и возвращает отсортированный список.
У нас есть две функции сортировки, sortByDueDate
и sortByPriority
, которые реализуют логику сортировки на основе срока выполнения и приоритета задач соответственно.
Класс TaskManager
управляет задачами и стратегиями сортировки. В нем есть список задач _tasks
и ссылка на TaskSortingStrategy
_sortingStrategy
. Метод addTask
добавляет задачу в список, метод setSortingStrategy
позволяет изменить стратегию сортировки во время выполнения, а метод getSortedTasks
возвращает отсортированный список задач на основе текущей стратегии сортировки.
Вот пример того, как вы можете использовать паттерн «Стратегия» в приложении «Список дел»:
void main() { TaskManager taskManager = TaskManager(sortByDueDate); Task task1 = Task(title: 'Task 1', dueDate: DateTime(2023, 6, 1), priority: 2); Task task2 = Task(title: 'Task 2', dueDate: DateTime(2023, 6, 2), priority: 1); Task task3 = Task(title: 'Task 3', dueDate: DateTime(2023, 6, 3), priority: 3); taskManager.addTask(task1); taskManager.addTask(task2); taskManager.addTask(task3); List<Task> sortedTasks = taskManager.getSortedTasks(); for (Task task in sortedTasks) { print('Task: ${task.title}, Due Date: ${task.dueDate}, Priority: ${task.priority}'); } }
В этом примере мы создаем экземпляр TaskManager
с sortByDueDate
в качестве начальной стратегии сортировки. Добавляем три задачи в файл TaskManager
.
Чтобы переключиться на другую стратегию сортировки, вы можете использовать taskManager.setSortingStrategy(sortByPriority)
.
На выходе будут отображаться задачи, отсортированные на основе выбранной стратегии сортировки.
7. Шаблон адаптера
Шаблон адаптера позволяет несовместимым интерфейсам работать вместе, предоставляя общий интерфейс, понятный обеим сторонам.
Вот пример того, как вы можете реализовать шаблон адаптера во Flutter для приложения со списком дел:
// External Library class ExternalTask { final String name; final String description; ExternalTask({required this.name, required this.description}); } // Adapter Interface abstract class TaskAdapter { String getTitle(); String getDescription(); } // Adapter class ExternalTaskAdapter implements TaskAdapter { final ExternalTask externalTask; ExternalTaskAdapter(this.externalTask); @override String getTitle() { return externalTask.name; } @override String getDescription() { return externalTask.description; } } // Client Code class Task { final String title; final String description; Task({required this.title, required this.description}); } void main() { // External Task from an external library ExternalTask externalTask = ExternalTask(name: 'External Task', description: 'External Task Description'); // Create an Adapter TaskAdapter adapter = ExternalTaskAdapter(externalTask); // Use the Adapter as a regular Task object Task task = Task(title: adapter.getTitle(), description: adapter.getDescription()); print('Task: ${task.title}'); print('Description: ${task.description}'); }
В приведенном выше примере у нас есть класс ExternalTask
, представляющий задачу из внешней библиотеки. Он имеет свойства name
и description
.
Интерфейс TaskAdapter
определяет методы getTitle()
и getDescription()
, которые должен реализовать адаптер. Он действует как мост между внешней библиотекой и нашим приложением.
Класс ExternalTaskAdapter
реализует интерфейс TaskAdapter
. Он принимает объект ExternalTask
в качестве входных данных и обеспечивает необходимые адаптации, чтобы сделать его совместимым с нашим классом Task
. Он внутренне делегирует вызовы соответствующим методам класса ExternalTask
.
В клиентском коде мы создаем экземпляр ExternalTask
из внешней библиотеки. Затем мы создаем адаптер ExternalTaskAdapter
с объектом ExternalTask
. Адаптер реализует интерфейс TaskAdapter
.
Теперь мы можем использовать адаптер как обычный объект Task
в нашем приложении. Мы создаем экземпляр Task
, передавая адаптированный заголовок и описание с помощью методов адаптера.
Наконец, мы печатаем title
и description
объекта Task
, что показывает, что адаптер успешно преобразует внешнюю задачу в наше внутреннее представление Task
.
Шаблон адаптера позволяет нам адаптировать несовместимые классы/интерфейсы, чтобы они работали без проблем. В этом случае ExternalTaskAdapter
действует как мост между ExternalTask
внешней библиотеки и нашим внутренним классом Task
.
8. Шаблон шаблона
Шаблон шаблона — это поведенческий шаблон проектирования, который позволяет определить скелет или структуру алгоритма в суперклассе, позволяя подклассам предоставлять конкретные реализации определенных шагов алгоритма. Он способствует повторному использованию кода и дает возможность определить общую структуру, обеспечивая при этом гибкость в деталях реализации.
Вот пример того, как вы можете реализовать шаблон шаблона во Flutter для приложения списка дел:
Рассмотрим сценарий, в котором вы хотите определить шаблон для сохранения задач в базе данных. Метод шаблона будет обрабатывать общие шаги сохранения, такие как открытие соединения с базой данных и его закрытие, в то время как подклассы предоставят конкретную реализацию для вставки задачи в базу данных.
// Abstract Task Repository abstract class TaskRepository { void saveTask(String task); void openConnection(); void closeConnection(); void saveToDatabase(String task); // Template Method void saveTaskToDatabase(String task) { openConnection(); saveToDatabase(task); closeConnection(); } } // Concrete Task Repository class FirebaseTaskRepository extends TaskRepository { @override void openConnection() { print('Opening connection to Firebase Database...'); } @override void closeConnection() { print('Closing connection to Firebase Database...'); } @override void saveToDatabase(String task) { print('Saving task to Firebase Database: $task'); // Implementation for saving task to Firebase } } // Client Code void main() { TaskRepository taskRepository = FirebaseTaskRepository(); taskRepository.saveTaskToDatabase('Task 1'); }
В приведенном выше примере у нас есть абстрактный класс TaskRepository
, представляющий шаблон для сохранения задач в базу данных. Он определяет метод шаблона saveTaskToDatabase
, который инкапсулирует общие шаги для сохранения задачи.
Абстрактный класс также объявляет абстрактные методы openConnection
, closeConnection
и saveToDatabase
, которые должны быть реализованы конкретными подклассами.
Затем у нас есть конкретный класс FirebaseTaskRepository
, который расширяет TaskRepository
и предоставляет конкретную реализацию для сохранения задач в базе данных Firebase.
В клиентском коде мы создаем экземпляр FirebaseTaskRepository
и вызываем метод saveTaskToDatabase
, который внутренне вызывает метод шаблона. Метод шаблона обрабатывает общие шаги открытия соединения, сохранения задачи в базе данных и закрытия соединения.
Вывод примера покажет выполнение метода шаблона с конкретными шагами, реализованными в классе FirebaseTaskRepository
.
9. Композитный шаблон
Составной шаблон — это структурный шаблон проектирования, который позволяет единообразно обрабатывать отдельные объекты и группы объектов, формируя древовидную иерархическую структуру. Это позволяет клиентам единообразно обрабатывать отдельные объекты и композиции объектов, упрощая код клиента и обеспечивая рекурсивные операции со всей структурой.
// Abstract Component abstract class TaskComponent { void add(TaskComponent component); void remove(TaskComponent component); void display(); } // Leaf Component: Individual Task class Task implements TaskComponent { final String name; Task(this.name); @override void add(TaskComponent component) { throw UnimplementedError('Cannot add to an individual task'); } @override void remove(TaskComponent component) { throw UnimplementedError('Cannot remove from an individual task'); } @override void display() { print('Task: $name'); } } // Composite Component: Task Category class TaskCategory implements TaskComponent { final String name; final List<TaskComponent> tasks; TaskCategory(this.name) : tasks = []; @override void add(TaskComponent component) { tasks.add(component); } @override void remove(TaskComponent component) { tasks.remove(component); } @override void display() { print('Category: $name'); for (var task in tasks) { task.display(); } } } // Client Code void main() { TaskComponent personalTasks = TaskCategory('Personal Tasks'); TaskComponent workTasks = TaskCategory('Work Tasks'); personalTasks.add(Task('Buy groceries')); personalTasks.add(Task('Pay bills')); workTasks.add(Task('Prepare presentation')); workTasks.add(Task('Attend meeting')); TaskComponent allTasks = TaskCategory('All Tasks'); allTasks.add(personalTasks); allTasks.add(workTasks); allTasks.display(); }
В приведенном выше примере у нас есть абстрактный класс TaskComponent
, который служит общим интерфейсом как для отдельных задач, так и для категорий задач. Он объявляет такие методы, как add
, remove
и display
, которые реализуются как конечными компонентами (отдельные задачи), так и составными компонентами (категориями задач).
Класс Task
представляет отдельную задачу, а класс TaskCategory
представляет категорию, которая может содержать несколько задач. Класс TaskCategory
имеет список объектов TaskComponent
для хранения своих дочерних компонентов.
В клиентском коде мы создаем экземпляры Task
и TaskCategory
для представления отдельных задач и категорий задач. Добавляем задачи в категории задач методом add
. Мы также можем удалить задачи, используя метод remove
, если это необходимо.
Мы создаем корень TaskCategory
с именем allTasks
и добавляем к нему категории personalTasks
и workTasks
. Наконец, мы вызываем метод display
для корневого компонента, чтобы отобразить иерархическую структуру задач и категорий.
В выходных данных примера будет показана иерархическая структура задач и категорий, демонстрирующая, как шаблон Composite позволяет единообразно обрабатывать отдельные задачи и категории задач.
10. Шаблон состояния
Шаблон State часто используется в сочетании с классами StatefulWidget и State для динамического управления состоянием и поведением виджетов.
Класс StatefulWidget представляет контекст или контейнер для управления состоянием, а класс State представляет различные состояния и обрабатывает их поведение. При изменении состояния StatefulWidget обновляет свою ссылку на новый объект State, в результате чего виджет перестраивается и отражает новое состояние и поведение.
Давайте рассмотрим сценарий, в котором вы хотите реализовать различное поведение задачи в зависимости от ее статуса завершения. Мы можем использовать шаблон состояния для управления состоянием задачи и динамического обновления ее внешнего вида и поведения.
import 'package:flutter/material.dart'; // Task Widget class TaskWidget extends StatefulWidget { @override _TaskWidgetState createState() => _TaskWidgetState(); } // Task State class _TaskWidgetState extends State<TaskWidget> { bool _isComplete = false; void toggleCompletionStatus() { setState(() { _isComplete = !_isComplete; }); } @override Widget build(BuildContext context) { return ListTile( title: Text('Task'), leading: Icon(_isComplete ? Icons.check_box : Icons.check_box_outline_blank), onTap: toggleCompletionStatus, ); } } // Main App void main() { runApp(MaterialApp( home: Scaffold( appBar: AppBar(title: Text('To-Do App')), body: ListView( children: [ TaskWidget(), TaskWidget(), ], ), ), )); }
В приведенном выше примере у нас есть TaskWidget
, представляющий элемент задачи в приложении списка дел. TaskWidget
— это StatefulWidget, что означает, что он имеет изменяемое состояние, которое может меняться со временем.
Класс _TaskWidgetState
представляет состояние класса TaskWidget
. Он содержит логическую переменную _isComplete
для отслеживания состояния завершения задачи. Метод toggleCompletionStatus
вызывается при касании задачи и обновляет состояние, переключая переменную _isComplete
с помощью setState
.
В методе build
_TaskWidgetState
мы используем текущее состояние _isComplete
для определения внешнего вида и поведения виджета задачи. Он отображает ListTile с заголовком, ведущим значком флажка (отмеченным или не отмеченным в зависимости от состояния завершения) и обратным вызовом onTap для переключения состояния завершения.
В основном приложении мы создаем MaterialApp с Scaffold и ListView, содержащим несколько экземпляров TaskWidget
для представления нескольких задач. Нажатие на задачу изменит статус ее завершения, а виджет перестроится, чтобы отразить обновленное состояние.
11.Шаблон фасада
Шаблон Facade — это структурный шаблон проектирования, который предоставляет упрощенный интерфейс или уровень абстракции более высокого уровня для доступа к сложным подсистемам или выполнения нескольких операций. Он скрывает сложности и детали базовой реализации и предоставляет клиентам простой и понятный интерфейс для взаимодействия.
Давайте рассмотрим сценарий, в котором вы хотите предоставить упрощенный интерфейс для обработки задач. Мы можем использовать шаблон Facade для создания класса ToDoFacade, который инкапсулирует сложные операции, связанные с управлением задачами.
// Complex Subsystem: TaskManager class TaskManager { void addTask(String taskName) { print('Task added: $taskName'); } void completeTask(String taskName) { print('Task completed: $taskName'); } void deleteTask(String taskName) { print('Task deleted: $taskName'); } } // Facade: ToDoFacade class ToDoFacade { final TaskManager _taskManager; ToDoFacade() : _taskManager = TaskManager(); void addTask(String taskName) { _taskManager.addTask(taskName); } void completeTask(String taskName) { _taskManager.completeTask(taskName); } void deleteTask(String taskName) { _taskManager.deleteTask(taskName); } } // Client Code void main() { ToDoFacade toDoFacade = ToDoFacade(); // Using the simplified interface provided by the facade toDoFacade.addTask('Buy groceries'); toDoFacade.completeTask('Buy groceries'); toDoFacade.deleteTask('Buy groceries'); }
В приведенном выше примере у нас есть сложная подсистема, представленная классом TaskManager
. Он предоставляет методы для добавления, завершения и удаления задач.
Класс ToDoFacade
служит фасадом, инкапсулируя сложность подсистемы TaskManager
. Он предоставляет клиентам упрощенный интерфейс для выполнения операций управления задачами. Фасад напрямую взаимодействует с TaskManager
для выполнения соответствующих операций.
В клиентском коде мы создаем экземпляр ToDoFacade
и используем его упрощенный интерфейс для добавления, выполнения и удаления задач. Клиентскому коду не нужно напрямую взаимодействовать с классом TaskManager
, так как фасад обрабатывает сложность внутри себя.
Вывод примера покажет выполнение операций управления задачами, но клиенты взаимодействуют только с упрощенным интерфейсом, предоставляемым фасадом.
12.Шаблон фасада
Шаблон интерпретатора — это поведенческий шаблон проектирования, который анализирует и интерпретирует текстовые выражения или команды. Он включает в себя определение грамматики, создание классов, представляющих различные выражения или элементы грамматики, и реализацию интерпретатора, который оценивает выражения на основе заданной грамматики.
Давайте рассмотрим сценарий, в котором вы хотите интерпретировать и выполнять команды для управления задачами. Мы можем использовать шаблон интерпретатора для анализа и выполнения простых текстовых команд для выполнения таких операций, как добавление, завершение или удаление задач.
// Context: TaskContext class TaskContext { List<String> tasks = []; void addTask(String taskName) { tasks.add(taskName); print('Task added: $taskName'); } void completeTask(String taskName) { tasks.remove(taskName); print('Task completed: $taskName'); } void deleteTask(String taskName) { tasks.remove(taskName); print('Task deleted: $taskName'); } } // Abstract Expression: Expression abstract class Expression { void interpret(TaskContext context); } // Concrete Expression: AddExpression class AddExpression implements Expression { final String taskName; AddExpression(this.taskName); @override void interpret(TaskContext context) { context.addTask(taskName); } } // Concrete Expression: CompleteExpression class CompleteExpression implements Expression { final String taskName; CompleteExpression(this.taskName); @override void interpret(TaskContext context) { context.completeTask(taskName); } } // Concrete Expression: DeleteExpression class DeleteExpression implements Expression { final String taskName; DeleteExpression(this.taskName); @override void interpret(TaskContext context) { context.deleteTask(taskName); } } // Client Code void main() { TaskContext context = TaskContext(); List<Expression> expressions = [ AddExpression('Buy groceries'), CompleteExpression('Buy groceries'), DeleteExpression('Buy groceries'), ]; for (Expression expression in expressions) { expression.interpret(context); } }
В приведенном выше примере у нас есть класс TaskContext
, который представляет контекст или среду для выполнения команд, связанных с управлением задачами. Он предоставляет такие методы, как addTask
, completeTask
и deleteTask
для выполнения соответствующих операций.
Интерфейс Command
определяет абстрактное выражение для интерпретации команд. Он объявляет метод interpret
, который принимает TaskContext
в качестве параметра и реализуется конкретными классами команд.
Конкретные классы команд, такие как AddCommand
, CompleteCommand
и DeleteCommand
, представляют собой специальные команды для добавления, завершения и удаления задач. Они реализуют метод interpret
и выполняют соответствующие операции над TaskContext
.
В клиентском коде мы создаем экземпляр TaskContext
и определяем список команд. Мы перебираем команды и вызываем метод interpret
для каждой команды, передавая TaskContext
в качестве контекста. Это интерпретирует и выполняет команды, выполняя соответствующие операции над задачами.
Вывод примера покажет выполнение команд, таких как добавление, завершение и удаление задач.
12.Шаблон итератора
Шаблон Iterator — это поведенческий шаблон проектирования, который обеспечивает способ последовательного доступа к элементам агрегатного объекта без раскрытия его базового представления. Он отделяет логику обхода от совокупного объекта, упрощая унифицированный обход элементов.
Давайте рассмотрим сценарий, в котором вы хотите перебрать набор задач. Мы можем использовать шаблон Iterator, чтобы обеспечить стандартизированный способ обхода и доступа к задачам в коллекции.
// Aggregate: TaskCollection class TaskCollection { List<String> tasks = []; void addTask(String taskName) { tasks.add(taskName); } TaskIterator getIterator() { return TaskIterator(this); } } // Iterator: TaskIterator class TaskIterator { final TaskCollection collection; int currentIndex; TaskIterator(this.collection) : currentIndex = 0; bool hasNext() { return currentIndex < collection.tasks.length; } String next() { if (hasNext()) { final task = collection.tasks[currentIndex]; currentIndex++; return task; } return null; } } // Client Code void main() { TaskCollection collection = TaskCollection(); collection.addTask('Task 1'); collection.addTask('Task 2'); collection.addTask('Task 3'); TaskIterator iterator = collection.getIterator(); while (iterator.hasNext()) { final task = iterator.next(); print(task); } }
В приведенном выше примере у нас есть класс TaskCollection
, представляющий совокупный объект или набор задач. Он содержит список задач и предоставляет метод addTask
для добавления задач в коллекцию. Метод getIterator
возвращает экземпляр TaskIterator
для перебора задач.
Класс TaskIterator
реализует интерфейс итератора и содержит ссылку на класс TaskCollection
. Он отслеживает текущий индекс при обходе задач. Метод hasNext
проверяет, есть ли еще задачи для итерации, а метод next
возвращает следующую задачу и продвигает текущий индекс.
В клиентском коде мы создаем экземпляр TaskCollection
и добавляем к нему несколько задач. Затем мы получаем итератор из коллекции, используя метод getIterator
. Затем мы можем использовать итератор для обхода задач с помощью цикла while
. Метод hasNext
используется для проверки наличия дополнительных задач, а метод next
используется для получения следующей задачи.
Вывод примера покажет, что задачи печатаются в последовательном порядке:
Task 1 Task 2 Task 3
13.Шаблон посетителя
Шаблон «Посетитель» — это поведенческий шаблон проектирования, который позволяет добавлять новые операции к существующим классам без изменения их структуры. Он отделяет операции или алгоритмы от объектов, с которыми они работают, предоставляя возможность добавлять новые операции без изменения самих классов.
Давайте рассмотрим сценарий, в котором у вас есть разные типы задач (например, личные задачи, рабочие задачи) и вы хотите выполнять разные операции для каждого типа задач. Мы можем использовать шаблон посетителя, чтобы определить интерфейс посетителя и конкретные классы посетителей для выполнения определенных операций над задачами.
// Element: Task abstract class Task { void accept(TaskVisitor visitor); } // Concrete Element: PersonalTask class PersonalTask implements Task { void accept(TaskVisitor visitor) { visitor.visitPersonalTask(this); } } // Concrete Element: WorkTask class WorkTask implements Task { void accept(TaskVisitor visitor) { visitor.visitWorkTask(this); } } // Visitor: TaskVisitor abstract class TaskVisitor { void visitPersonalTask(PersonalTask task); void visitWorkTask(WorkTask task); } // Concrete Visitor: TaskPrinterVisitor class TaskPrinterVisitor implements TaskVisitor { void visitPersonalTask(PersonalTask task) { print('Processing personal task'); // Perform specific operations for personal tasks } void visitWorkTask(WorkTask task) { print('Processing work task'); // Perform specific operations for work tasks } } // Client Code void main() { List<Task> tasks = [ PersonalTask(), WorkTask(), PersonalTask(), ]; TaskVisitor visitor = TaskPrinterVisitor(); for (Task task in tasks) { task.accept(visitor); } }
В приведенном выше примере у нас есть абстрактный класс Task
, который представляет элемент в шаблоне посетителя. Он объявляет метод accept
, который принимает TaskVisitor
в качестве аргумента.
Конкретные классы PersonalTask
и WorkTask
реализуют интерфейс Task
и предоставляют свои собственные реализации метода accept
. Они вызывают соответствующий метод посещения посетителя, передавая себя в качестве аргумента.
Интерфейс TaskVisitor
объявляет методы посещения для каждого типа задач. В этом примере у нас есть методы visitPersonalTask
и visitWorkTask
.
Конкретный класс TaskPrinterVisitor
реализует интерфейс TaskVisitor
и предоставляет конкретные реализации для каждого метода посещения. В этом примере он просто печатает сообщение, указывающее тип обрабатываемой задачи.
В клиентском коде мы создаем список задач, включая объекты PersonalTask
и WorkTask
. Мы также создаем экземпляр посетителя TaskPrinterVisitor
.
Затем мы перебираем задачи и вызываем метод accept
для каждой задачи, передавая посетителя в качестве аргумента. Это вызывает соответствующий метод посещения посетителя в зависимости от типа задачи.
В выводе примера будут отображаться сообщения, указывающие на тип обрабатываемой задачи:
Processing personal task Processing work task Processing personal task
Понимание и использование шаблонов проектирования во Flutter позволяет разработчикам принимать обоснованные проектные решения, улучшать качество кода и создавать масштабируемые и удобные в сопровождении приложения. Это позволяет лучше разделить проблемы, упростить тестирование и обеспечить гибкость для будущих изменений.
Они служат мощными инструментами в мобильной разработке Flutter, позволяя разработчикам создавать хорошо структурированные, удобные в сопровождении и расширяемые приложения, придерживаясь лучших практик и отраслевых стандартов. Используя правильные шаблоны проектирования, разработчики могут оптимизировать процесс разработки, повысить качество кода и обеспечить исключительный пользовательский опыт.
👏🏽 Похлопайте этой истории
👉🏽 Подпишитесь на следующие статьи
💰 Доступ к бесплатным руководствам по мобильной разработке
🔔 Подпишитесь на ещё
Увидимся в следующей статье👋