Если вы занимаетесь программированием на Flutter, то наверняка сталкивались с задачами, которые можно было бы решить более эффективно и легко, используя уже проверенные практиками решения. В этом и помогают паттерны проектирования на Dart — шаблоны, которые разработчики применяют для решения часто встречающихся проблем. В двух статьях команда Mad Brains рассмотрит 16 паттернов проектирования на Dart, как они могут быть использованы для улучшения качества кода и повышения эффективности разработки.

Singleton
Паттерн Singleton (Одиночка) является порождающим паттерном проектирования.Цель паттерна заключается в том, чтобы у класса мог быть только единственный экземпляр во всей программе и к нему была предоставлена глобальная точка доступа.
Реализация: сокрытие конструктора класса и создание статического метода / поля / геттера, предоставляющего доступ к экземпляру класса.
Пример кода
class Logger { Logger._internal(); static final Logger _instance = Logger._internal(); static Logger get instance => _instance; } void main() { final Logger logger = Logger.instance; final Logger anotherOneLogger = Logger.instance; print(identical(logger, anotherOneLogger)); // Output: true }
Плюсы
-
Дает гарантию, что в программе может быть только один экземпляр класса;
-
Предоставляет глобальную точку доступа к экземпляру.
Минусы
-
Имеет проблемы с потоками в многопоточных языках;
-
Требует особой тактики тестирования при юнит-тестировании.
Abstract factory
Паттерн Abstract factory (Абстрактная фабрика) является порождающим паттерном проектирования.Целью паттерна является возможность создания семейства связанных объектов, не привязываясь к их конкретным классам.
Реализация:
-
Выделение общих интерфейсов для семейств объектов.
-
Определение интерфейса абстрактной фабрики, имеющего методы для создания каждого из типов семейств объектов.
-
Создание для каждого семейства объектов конкретного класса фабрики, реализующего интерфейс абстрактной фабрики.
Пример кода
/// Общий интерфейс для кнопок abstract class Button { const Button(); void paint(); } /// Конкретная кнопка - Android class AndroidButton implements Button { const AndroidButton(); @override void paint() => print('AndroidButton is painted'); } /// Конкретная кнопка - IOS class IOSButton implements Button { const IOSButton(); @override void paint() => print('IOSButton is painted'); } /// Общий интерфейс для чекбоксов abstract class CheckBox { const CheckBox(); void paint(); } /// Конкретный чекбокс - Android class AndroidCheckBox implements CheckBox { const AndroidCheckBox(); @override void paint() => print('AndroidCheckBox is painted'); } /// Конкретный чекбокс - IOS class IOSCheckBox implements CheckBox { const IOSCheckBox(); @override void paint() => print('IOSCheckBox is painted'); } /// Интерфейс абстрактной фабрики abstract class GUIFactory { const GUIFactory(); Button createButton(); CheckBox createCheckBox(); } /// Конкретная фабрика - Android class AndroidFactory implements GUIFactory { const AndroidFactory(); @override Button createButton() => AndroidButton(); @override CheckBox createCheckBox() => AndroidCheckBox(); } /// Конкретная фабрика - IOS class IOSFactory implements GUIFactory { const IOSFactory(); @override Button createButton() => IOSButton(); @override CheckBox createCheckBox() => IOSCheckBox(); } // Клиент, использующий абстрактную фабрику. Клиент не привязывается к // конкретным классам объектов и может работать с любыми вариациями семейства // объектов благодаря их абстрактным интерфейсам. class Application { Application(this._factory) { _button = _factory.createButton(); _checkBox = _factory.createCheckBox(); } final GUIFactory _factory; late final Button _button; late final CheckBox _checkBox; void paint() { _button.paint(); _checkBox.paint(); } } void main() { late Application app; app = Application(IOSFactory()); app.paint(); // Output: 'IOSButton is painted\nIOSCheckBox is painted' app = Application(AndroidFactory()); app.paint(); // Output: 'AndroidButton is painted\nAndroidCheckBox is painted' }
Плюсы
-
Реализует Принцип открытости/закрытости;
-
Упрощает замену и добавление новых семейств продуктов;
-
Гарантирует сочетаемость продуктов;
-
Избавляет клиентский код от привязки к конкретным классам продуктов.
Минусы
-
Усложняет код программы из-за введения множества дополнительных классов.
Adapter
Паттерн Adapter (Wrapper, Адаптер) является структурным паттерном проектирования.Цель паттерна заключается в обеспечении возможности совместной работы объектов с несовместимыми интерфейсами.
Реализация:
-
Создание класса адаптера, реализующего интерфейс, который ожидает клиент.
-
Поместить в адаптер существующий класс с нужным функционалом, но не совместимым с интерфейсом, ожидаемом в клиенте.
-
Реализовать в адаптере все методы интерфейса, который ожидает клиент, делегируя всю работу помещенному в адаптер классу.
Пример кода
/// Интерфейс, который ожидает клиент abstract class Logger { void log(String message); } /// Существующий класс, имеющий желаемую функциональность, но несовместимый интерфейс class ConsoleLogger { void consoleLog(String message) => print(message); } /// Класс - адаптер, адаптирующий [ConsoleLogger] к интерфейсу [Logger] class ConsoleLoggerAdapter implements Logger { final ConsoleLogger _consoleLogger; ConsoleLoggerAdapter(this._consoleLogger); @override void log(String message) => _consoleLogger.consoleLog(message); } /// Клиент использует [ConsoleLoggerAdapter] для взаимодействия с [ConsoleLogger] void main() { final Logger logger = ConsoleLoggerAdapter(ConsoleLogger()); logger.log('Hello, World!'); // Output: 'Hello, World!' }
Плюсы
-
Позволяет повторно использовать имеющийся объект, адаптируя его несовместимый интерфейс, отделяя и скрывая от клиента подробности преобразования.
Минусы
-
Усложняет код программы из-за введения дополнительных классов.
Decorator
Паттерн Decorator (Декоратор, Обертка) является структурным паттерном проектирования.Цель паттерна заключается в предоставлении возможности динамического добавления объектам новой функциональности, оборачивая их в классы-обёртки.
Реализация:
-
Создание интерфейса компонента, описывающего общие методы как для конкретного компонента, так и для его декораторов.
-
Создание класса конкретного компонента, содержащего основную бизнес-логику. Конкретный компонент должен следовать интерфейсу компонента.
-
Создание базового класса для декораторов. Он должен хранить в себе ссылку на вложенный компонент. Базовый декоратор должен следовать тому же интерфейсу компонента, что и конкретный компонент.
-
Создание классов конкретных декораторов, наследующих базовый. Конкретный декоратор должен выполнять свою добавочную функцию до или после вызова этой же функции обёрнутого объекта.
Пример кода
/// Интерфейс компонента abstract class TextEditor { const TextEditor(); abstract final String text; } /// Конкретный компонент class SimpleTextEditor implements TextEditor { const SimpleTextEditor(this.text); @override final String text; } /// Базовый класс для декораторов abstract class TextEditorDecorator implements TextEditor { const TextEditorDecorator(this.textEditor); final TextEditor textEditor; } /// Конкретный декоратор class BoldTextDecorator extends TextEditorDecorator { const BoldTextDecorator(super.textEditor); @override String get text => '<b>${textEditor.text}</b>'; } /// Конкретный декоратор class ItalicTextDecorator extends TextEditorDecorator { const ItalicTextDecorator(super.textEditor); @override String get text => '<i>${textEditor.text}</i>'; } void main() { TextEditor editor = SimpleTextEditor('Hello world!'); print(editor.text); // Output: 'Hello, World!' editor = BoldTextDecorator(editor); print(editor.text); // Output: '<b>Hello, World!</b>' editor = ItalicTextDecorator(editor); print(editor.text); // Output: '<i><b>Hello, World!</b></i>' }
Плюсы
-
Позволяет динамически добавлять одну или несколько новых обязанностей;
-
Имеет большую гибкость, чем у наследования.
Минусы
-
Множество мелких классов.
Если эта статья кажется вам простой, то огонь — приходите к нам работать! У нас открыта вакансия Flutter-разработчика, подробности можно узнать на сайте Mad Brains в разделе «Карьера».
Command
Паттерн Command (Команда) является поведенческим паттерном проектирования.Цель паттерна заключается в представлении действий как объектов, заключающих в себе само действие и его параметры, что позволяет сохранять историю действий, ставить их в очередь, поддерживать их отмену и повтор.
С паттерном Команда всегда связаны четыре термина:
-
Команды (Command) — классы, представляющие действие как объект.
-
Получатель (Приемник, Receiver) — класс, содержащий реализацию действий, команды делегируют ему свои действия, вызывая его методы.
-
Отправитель (Invoker) — класс, вызывающий команды. Работает с командами только через их общий интерфейс, не зная ничего о конкретных командах. Отправитель может вести учёт и запись выполненных команд.
-
Клиент (Client) — создает объекты конкретных команд и связывает их с отправителем для их выполнения.
Реализация:
-
Создание общего интерфейса для команд, определение в нем метода вызова команды.
-
Создание классов конкретных команд. В каждом классе должно быть поле, хранящее объект-получатель, которому команда будет делегировать свою работу. При необходимости команда должна получать через конструктор и хранить в себе поля параметров, необходимых для вызова методов получателя.
-
Добавление в класс отправитель метода для вызова команды. Отправитель может как хранить в себе команды для их вызова, так и принимать команду в методе для ее вызова, либо иметь сеттер для поля команды.
-
Создание в клиенте объекта получателя, объектов команд и их связь с отправителем.
Пример кода
/// Получатель class TVControllerReceiver { TVControllerReceiver(); int _currentChannel = 0; int _currentVolume = 0; int get currentChannel => _currentChannel; int get currentVolume => _currentVolume; void channelNext() { _currentChannel++; print('changed channel to next, now current is $_currentChannel'); } void channelPrevious() { _currentChannel--; print('changed channel to previous, now current is $_currentChannel'); } void volumeUp() { _currentVolume++; print('volume up, now current is $_currentVolume'); } void volumeDown() { _currentVolume--; print('volume down, now current is $_currentVolume'); } } /// Интерфейс команды abstract class Command { abstract final TVControllerReceiver receiver; void execute(); } /// Конкретная команда class ChannelNextCommand implements Command { ChannelNextCommand(this.receiver); @override final TVControllerReceiver receiver; @override void execute() => receiver.channelNext(); } /// Конкретная команда class ChannelPreviousCommand implements Command { ChannelPreviousCommand(this.receiver); @override final TVControllerReceiver receiver; @override void execute() => receiver.channelPrevious(); } /// Конкретная команда class VolumeUpCommand implements Command { VolumeUpCommand(this.receiver); @override final TVControllerReceiver receiver; @override void execute() => receiver.volumeUp(); } /// Конкретная команда class VolumeDownCommand implements Command { VolumeDownCommand(this.receiver); @override final TVControllerReceiver receiver; @override void execute() => receiver.volumeDown(); } /// Отправитель class TVControllerInvoker { TVControllerInvoker(); Command? _lastCommand; final List<String> _logs = []; void executeCommand(Command command) { command.execute(); _lastCommand = command; _logs.add('${DateTime.now()} ${command.runtimeType}'); } void repeatLastCommand() { final Command? command = _lastCommand; if (command != null) executeCommand(command); } void logHistory() { for (final String log in _logs) { print(log); } } } void main() { final TVControllerReceiver receiver = TVControllerReceiver(); final TVControllerInvoker invoker = TVControllerInvoker(); invoker.executeCommand(ChannelNextCommand(receiver)); invoker.executeCommand(VolumeUpCommand(receiver)); invoker.repeatLastCommand(); invoker.logHistory(); }
Плюсы
-
Реализует Принцип открытости/закрытости;
-
Позволяет реализовать отмену и повтор операций, хранить историю их выполнения;
-
Позволяет собирать сложные команды из простых;
-
Позволяет реализовать отложенный запуск операций (ставить их в очередь).
Минусы
-
Усложняет код из-за необходимсоти создания множества дополнительных классов.
Visitor
Паттерн Visitor (Посетитель) является поведенческим паттерном проектирования.Цель паттерна заключается в предоставлении возможности добавлять в программу новые операции над объектами других классов без необходимости изменения этих классов.
Реализация:
-
Создание интерфейса посетителя и объявление в нем методов посещения
visit()для каждого класса, над которым будет выполняться операция «посещения». -
Реализация метода принятия
accept(Visitor visitor)посетителя в интерфейсе или базовом классе иерархии элементов, над которыми будет производиться операция «посещения».Иерархия элементов должна знать только о базовом интерфейсе посетителей, в то время как посетители будут знать о всех подклассах иерархии элементов. -
Реализация метода принятия
accept(Visitor visitor)посетителя во всех конкретных элементах иерархии. Каждый конкретный элемент должен делегировать выполнение методаaccept(Visitor visitor)тому методу посетителя, в котором тип параметра совпадает с текущим классом элемента. -
Создание новых конкретных посетителей для каждого нового действия над элементами иерархии. Конкретный посетитель должен реализовывать все методы интерфейса посетителей, выполняя требуемое действие.
-
Клиенты создают объекты посетителей и передают их каждому элементу, использую метод принятия
accept(Visitor visitor).
Пример кода
/// Базовый класс иерархии элементов пользователей соц. сетей abstract class SocialNetworkUser { const SocialNetworkUser(); abstract final String name; abstract final String link; void accept(Visitor visitor); } /// Конкретный класс иерархии элементов пользователей соц. сетей class VKUser extends SocialNetworkUser { const VKUser({required this.name, required this.link}); @override final String name; @override final String link; @override void accept(Visitor visitor) => visitor.visitVKUser(this); } /// Конкретный класс иерархии элементов пользователей соц. сетей class TelegramUser extends SocialNetworkUser { const TelegramUser({ required this.name, required this.link, this.phoneNumber, }); @override final String name; @override final String link; final String? phoneNumber; @override void accept(Visitor visitor) => visitor.visitTelegramUser(this); } /// Интерфейс посетителя с объявленными методами посещения каждого класса иерархии abstract class Visitor { void visitVKUser(VKUser user); void visitTelegramUser(TelegramUser user); } /// Конкретный интерфейс, выполняющий действие class LogInfoVisitor implements Visitor { @override void visitVKUser(VKUser user) { print('${user.runtimeType} - ${user.name} - ${user.link}'); } @override void visitTelegramUser(TelegramUser user) { final String phoneNumber = user.phoneNumber ?? 'number hidden'; print('${user.runtimeType} - ${user.name} - ${user.link} - $phoneNumber'); } } void main() { const List<SocialNetworkUser> users = [ VKUser(name: 'Павел Дуров', link: 'vk.com/id1'), VKUser(name: 'Дмитрий Медведев', link: 'vk.com/dm'), TelegramUser(name: 'Ivan', link: 't.me/ivan', phoneNumber: '+78005553535'), TelegramUser(name: 'Anonym', link: 't.me/anon'), ]; final LogInfoVisitor logInfoVisitor = LogInfoVisitor(); for (final SocialNetworkUser user in users) { user.accept(logInfoVisitor); } }
Плюсы
-
Упрощает добавление новых операций над элементами;
-
Объединяет родственные операции в классе
Visitor; -
Дает возможность запоминать состояние по мере обхода элементов.
Минусы
-
Затрудняет добавление новых классов в иерархию, так как каждый раз придется обновлять интерфейс посетителя и его конкретные подклассы.
Если вам интересно развиваться во Flutter, хорошая новость: у нас открыта вакансия Flutter-разработчика, подробности можно узнать на сайте Mad Brains в разделе «Карьера».
Вторая часть статьи выйдет уже на следующей неделе. В комментариях пишите, что было полезно, и что хотели бы узнать о технологии подробнее.
ссылка на оригинал статьи https://habr.com/ru/articles/736364/
Добавить комментарий