Шаблон проектирования – это многократно используемое решение часто возникающей проблемы при проектировании и разработке программного обеспечения.

Преимущества шаблонов проектирования:

  1. Возможность повторного использования: шаблоны проектирования инкапсулируют повторно используемые решения, позволяя разработчикам применять проверенные подходы к аналогичным проблемам проектирования. Это снижает потребность в повторном изобретении решений и способствует повторному использованию кода.
  2. Ремонтопригодность: шаблоны проектирования обеспечивают четкую и организованную структуру кода, что облегчает его понимание и поддержку. Они предоставляют общий язык и структуру для разработчиков, улучшая совместную работу и общение внутри команд.
  3. Масштабируемость. Шаблоны проектирования помогают создавать масштабируемые программные системы, предоставляя рекомендации по управлению сложностью и позволяя легко расширять или модифицировать существующий код без нарушения работы системы.
  4. Гибкость. Шаблоны проектирования позволяют разработчикам создавать гибкое программное обеспечение, способное адаптироваться к изменяющимся требованиям. Они разъединяют компоненты и способствуют слабой связи, упрощая модификацию или замену отдельных элементов системы, не затрагивая другие части.
  5. Производительность. Некоторые шаблоны проектирования ориентированы на оптимизацию производительности и использования ресурсов. Они предоставляют методы для эффективного создания объектов, кэширования, отложенной загрузки и других соображений, связанных с производительностью.

Давайте углубимся в некоторые из наиболее часто используемых шаблонов проектирования в среде Flutter и поймем, как они могут помочь нам создавать надежные и удобные в сопровождении приложения Flutter.

  1. Шаблон одиночного элемента:

Шаблон 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, позволяя разработчикам создавать хорошо структурированные, удобные в сопровождении и расширяемые приложения, придерживаясь лучших практик и отраслевых стандартов. Используя правильные шаблоны проектирования, разработчики могут оптимизировать процесс разработки, повысить качество кода и обеспечить исключительный пользовательский опыт.

👏🏽 Похлопайте этой истории

👉🏽 Подпишитесь на следующие статьи

💰 Доступ к бесплатным руководствам по мобильной разработке

🔔 Подпишитесь на ещё

Увидимся в следующей статье👋