Паттерны проектирования на Dart с примерами кода. Часть 2
Первую часть можно прочитать тут.
Observer
Паттерн «Наблюдатель» (Observer) — это поведенческий паттерн, который позволяет объекту, называемому «наблюдателем», получать автоматическое уведомление об изменениях состояния другого объекта, называемого «наблюдаемым» или «субъектом».
Реализация:
- Создать интерфейс или абстрактный класс для субъекта, содержащий методы для добавления, удаления и уведомления наблюдателей.
- Создать интерфейс или абстрактный класс для наблюдателя, содержащий метод обработки уведомления.
- Реализовать интерфейсы или абстрактные классы в конкретных классах субъекта и наблюдателя.
- В классе субъекта хранить список наблюдателей и вызывать метод уведомления у каждого из них при изменении состояния.
Пример кода
abstract class Observable {
void addObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
class ConcreteObservable implements Observable {
final List _observers = [];
int? _veryUsefulNumber;
set veryUsefulNumber(int number) {
_veryUsefulNumber = number;
notifyObservers();
}
@override
void addObserver(Observer observer) {
_observers.add(observer);
}
@override
void removeObserver(Observer observer) {
_observers.remove(observer);
}
@override
void notifyObservers() {
for (final observer in _observers) {
observer.update(_veryUsefulNumber);
}
}
}
abstract class Observer {
void update(int? data);
}
class ConcreteObserver implements Observer {
@override
void update(int? data) {
print(’now data is $data’);
}
}
void main() {
final ConcreteObservable observable = ConcreteObservable();
final Observer observer = ConcreteObserver();
observable.addObserver(observer);
for (int i = 0; i < 5; i++) {
observable.veryUsefulNumber = i;
}
observable.removeObserver(observer);
}
В данном примере кода, интерфейс Observable определяет методы для управления наблюдателями и оповещения их об изменениях. ConcreteObservable реализует этот интерфейс, храня список наблюдателей и оповещая их об изменении нашего очень полезного числа. Интерфейс Observer определяет метод update для получения уведомлений об изменениях состояния и ConcreteObserver реализует этот метод, печатая данные.
В методе main создаются объекты ConcreteObservable и ConcreteObserver, конкретный наблюдатель добавляется к конкретному наблюдаемому и обновляется значение поля, после он будет оповещен об этом изменении. После нескольких изменений наблюдатель удаляется из списка наблюдаемых.
Плюсы:
- Разделение объектов на две роли: наблюдаемый и наблюдатель. Это позволяет им изменяться независимо друг от друга, что делает код более масштабируемым и сопровождаемым.
- Несколько объектов могут одновременно наблюдать за изменениями одного объекта, что очень полезно в ситуациях, когда несколько объектов должны одновременно реагировать на изменения в другом объекте.
- Позволяет динамически добавлять и удалять наблюдателей без изменения кода наблюдаемого объекта.
Минусы:
- Перегрузка сообщений: если наблюдателей слишком много, они могут получать много лишних сообщений, что может привести к низкой производительности и перегрузке системы.
- Циклическая зависимость: если наблюдатель и наблюдаемый зависят друг от друга, то это может привести к циклической зависимости и неожиданным последствиям.
- Неоднозначность порядка обновления: если несколько наблюдателей обновляются одновременно, то может возникнуть проблема с неоднозначностью порядка обновления.
Паттерн «Наблюдатель» используется во Flutter: например, StreamBuilder и ChangeNotifier.
Memento
Паттерн «Хранитель» (Memento) — это паттерн проектирования, который позволяет сохранять и восстанавливать состояние объекта без нарушения инкапсуляции.
Реализация:
- Создать класс Originator, который хранит текущее состояние и создает объект Memento с текущим состоянием.
- Создать класс Memento, который содержит состояние Originator и недоступен для изменения из вне.
- Создать класс Caretaker, который запрашивает и восстанавливает состояние Originator с помощью объекта Memento.
- В Originator определить методы сохранения и восстановления состояния с использованием Memento
- В Caretaker определить методы сохранения Memento и доступа к нему.
Пример кода
class FormState {
const FormState(this._email, this._password);
final String _email;
String get email => _email;
final String _password;
String get password => _password;
}
class FormStateStore {
FormState? state;
}
class FormController {
String email = ’’;
String password = ’’;
FormState saveState() {
return FormState(email, password);
}
void restoreState(FormState state) {
email = state.email;
password = state.password;
}
void showState() {
print(’Email: $email’);
print(’Password: $password’);
}
}
void main() {
final FormController formController = FormController();
final FormStateStore formStateStore = FormStateStore();
formController.email = ’foo@example.com’;
formController.password = ’12345678′;
print(’______ INITIAL STATE ______’);
formController.showState();
print(’___________________________ ’);
formStateStore.state = formController.saveState();
formController.email = ’bar@example.com’;
formController.password = ’qwerty123′;
print(’______ CHANGED STATE ______’);
formController.showState();
print(’___________________________ ’);
final FormState? savedState = formStateStore.state;
if (savedState != null) {
formController.restoreState(savedState);
}
print(’______ RESTORED STATE ______’);
formController.showState();
print(’____________________________’);
}
В приведенном коде определены класс FormState, который хранит состояние формы, класс FormStateStore — хранит состояние формы, и класс FormController, который управляет состоянием формы. Метод saveState сохраняет состояние формы в объекте FormState и метод restoreState восстанавливает состояние формы из объекта FormState.
В методе main сначала создается экземпляр FormController, затем меняется состояние формы, далее сохраняется состояние формы в FormStateStore и изменяется состояние формы снова. Затем используется сохраненное состояние для восстановления состояния формы. Это демонстрирует, как паттерн Memento используется для сохранения и восстановления состояния объекта без нарушения его инкапсуляции.
Важно отметить, что состояние сохраняется внешним объектом (FormStateStore), это позволяет отделить объект, хранящий состояние, от объекта, который управляет этим состоянием. Также важно, что состояние сохраняется в виде отдельного объекта, это позволяет сохранять несколько разных состояний одного и того же объекта. И важно, что интерфейс сохраняемого объекта должен быть достаточно простым, чтобы снизить связанность между объектом, который сохраняет состояние, и объектом, который его восстанавливает.
Плюсы:
- Позволяет сохранять и восстанавливать состояние объекта без нарушения его инкапсуляции.
- Обеспечивает возможность отменять действия и создание механизма отмены/возврата действий.
- Позволяет сохранять несколько разных состояний одного и того же объекта.
- Отделяет объект хранящий состояние от объекта управляющего этим состоянием, что упрощает и оптимизирует реализацию и использование объекта.
Минусы:
- Необходимость создания дополнительных классов для хранения состояния, которые могут усложнять структуру кода.
- Возможность создания большого количества объектов, для хранения состояния, которые могут приводить к увеличению использования памяти.
- Необходимость реализовать механизм отмены/возврата действий может быть сложной и затратной в реализации
Во Flutter паттерн Memento может быть использован для сохранения и восстановления состояния форм или состояния после навигации в приложении.
Responsibility chain
Паттерн Responsibility chain («Цепочка ответственности») — это структурный паттерн проектирования, который используется для организации в системе иерархии объектов, каждый из которых может обрабатывать запрос. Каждый объект в цепочке решает, может ли он обработать запрос или же передать его следующему объекту в цепочке.
Реализация:
- Создать интерфейс или абстрактный класс для обработчиков, который будет содержать метод для обработки запросов и указания следующего обработчика.
- Реализовать конкретные классы обработчиков, которые будут реализовывать интерфейс или абстрактный класс и обрабатывать запросы.
- Установить цепочку ответственности связывая обработчики в определенном порядке.
- Создать класс запроса и передавать его в начало цепочки ответственности
Пример кода
enum MessageSender {
colleague(1),
friends(2),
family(3),
other(0);
const MessageSender(this.level);
final int level;
}
abstract class MessageHandler {
MessageHandler(this._handleSender);
final MessageSender _handleSender;
MessageHandler? _nextHandler;
set nextHandler(MessageHandler handler) => _nextHandler = handler;
void handle(String message, MessageSender sender) {
if (_handleSender.level <= sender.level) {
_handleMessage(message);
}
_nextHandler?.handle(message, sender);
}
void _handleMessage(String message);
}
class EmailMessageHandler extends MessageHandler {
EmailMessageHandler(super.handleSender);
@override
void _handleMessage(String message) {
print(’Message «$message» send to e-mail’);
}
}
class SmsMessageHandler extends MessageHandler {
SmsMessageHandler(super.handleSender);
@override
void _handleMessage(String message) {
print(’Message «$message» send to sms’);
}
}
class TrashBoxMessageHandler extends MessageHandler {
TrashBoxMessageHandler(super.handleSender);
@override
void _handleMessage(String message) {
print(’Message «$message» deleted’);
}
}
void main() {
final MessageHandler emailHandler = EmailMessageHandler(MessageSender.friends);
final MessageHandler smsHandler = SmsMessageHandler(MessageSender.family);
final MessageHandler trashBoxHandler = TrashBoxMessageHandler(MessageSender.other);
emailHandler.nextHandler = smsHandler;
smsHandler.nextHandler = trashBoxHandler;
emailHandler.handle(’Hello, I’m your very far sibling from Nigeria...’, MessageSender.other);
}
В приведенном примере кода есть 3 типа обработчика сообщений: EmailMessageHandler, SmsMessageHandler и TrashBoxMessageHandler. Каждый обработчик имеет уровень ответственности (MessageSender), по которому он может обрабатывать сообщения. Если обработчик не может обработать сообщение, то он передает его следующему обработчику в цепочке.
Плюсы:
- Разделение ответственности: каждый объект в цепочке отвечает за свою часть обработки запроса, что делает код более структурированным и легко масштабируемым.
- Легкость изменения: вы можете легко добавить или удалить обработчик из цепочки, без необходимости вносить изменения в клиентский код.
- Независимость обработчиков: каждый обработчик не зависит от конкретного типа запроса
- Возможность просмотра состояния запроса: каждый обработчик может просмотреть и изменить состояние запроса в процессе обработки, что может быть полезно для добавления дополнительной информации к запросу или его изменения.
Минусы:
- Сложность отладки: если что-то идет не так в процессе обработки запроса, может быть трудно определить, где именно произошла ошибка.
- Потенциальная задержка: если некоторые из обработчиков в цепочке перегружены или неоптимизированы, это может привести к задержкам в обработке запросов и увеличению нагрузки на систему.
Во Flutter можно использовать этот паттерн для обработки событий ввода (например, нажатий на кнопки) или валидации ввода в формах. Например, можно создать набор обработчиков, каждый из которых отвечает за определенный тип валидации (например, проверка длины пароля или корректности email-адреса) и настроить их в нужной последовательности, чтобы запустить валидацию в нужном порядке.
Strategy
Паттерн Strategy («Стратегия») — это поведенческий шаблон проектирования, который позволяет выбирать поведение алгоритма во время выполнения. Этот шаблон определяет семейство алгоритмов, инкапсулирует каждый из них и делает их взаимозаменяемыми. Шаблон стратегии позволяет клиенту выбирать, какой алгоритм использовать.
Реализация:
- Создать интерфейс или абстрактный класс для стратегий, который будет содержать метод для выполнения определенного алгоритма.
- Реализовать конкретные классы стратегий, которые будут реализовывать интерфейс или абстрактный класс и реализовывать различные алгоритмы.
- Создать класс контекста, который будет хранить ссылку на стратегию и предоставлять метод для ее изменения.
- Использовать класс контекста для вызова соответствующей стратегии
Пример кода
abstract class DeliveryMethod {
void calculateDeliveryTime();
void calculateDeliveryPrice();
}
class CourierDeliveryMethod implements DeliveryMethod {
const CourierDeliveryMethod(this._distanceToUserInMeters);
final int _distanceToUserInMeters;
static const int _minimumCost = 100;
@override
void calculateDeliveryPrice() {
print(’The cost of courier delivery is ${_minimumCost + (_distanceToUserInMeters / 10).ceil()} RUB’);
}
@override
void calculateDeliveryTime() {
final DateTime estimatedDeliveryTime = DateTime.now().add(const Duration(days: 2));
print(
’The courier will arrive on ${estimatedDeliveryTime.day}.${estimatedDeliveryTime.month}.${estimatedDeliveryTime.year}’,
);
}
}
class PickupDeliveryMethod implements DeliveryMethod {
const PickupDeliveryMethod(this._packageWeightInKilograms);
final double _packageWeightInKilograms;
static const int _minimumCost = 29;
@override
void calculateDeliveryPrice() {
print(’The cost of package pickup is ${_minimumCost + (_packageWeightInKilograms * 100).ceil()} RUB’);
}
@override
void calculateDeliveryTime() {
print(’Your package will arrive at pickup point in 5 days’);
}
}
class DeliverySelector {
DeliverySelector(this._packageWeightInKilograms, this._distanceToUserInMeters);
DeliveryMethod? _method;
final double _packageWeightInKilograms;
final int _distanceToUserInMeters;
void selectPickup() {
_method = PickupDeliveryMethod(_packageWeightInKilograms);
}
void selectCourier() {
_method = CourierDeliveryMethod(_distanceToUserInMeters);
}
void showDeliveryInfo() {
print(’______ DELIVERY INFO ______’);
_method?.calculateDeliveryTime();
_method?.calculateDeliveryPrice();
print(’___________________________ ’);
}
}
void main() {
final DeliverySelector selector = DeliverySelector(0.5, 1203);
selector.selectPickup();
selector.showDeliveryInfo();
selector.selectCourier();
selector.showDeliveryInfo();
}
В этом примере DeliveryMethod — это абстрактный класс, представляющий метод доставки. Существуют две различные конкретные реализации этого класса: CourierDeliveryMethod и PickupDeliveryMethod. Оба этих класса предоставляют способ расчета времени доставки и цены.
Класс DeliverySelector отвечает за выбор способа доставки (самовывоз или курьер). Клиент может переключаться между этими двумя методами доставки во время выполнения. Класс DeliverySelector также имеет метод с именем showDeliveryInfo, который выводит время доставки и цену для выбранного в данный момент способа доставки.
Плюсы:
- Возможность переключения между алгоритмами во время выполнения.
- Простое добавление новых алгоритмов без изменения существующего клиентского кода.
- Возможность использовать алгоритм независимо от клиента.
- Повышение гибкости кода.
Минусы:
- Увеличение количества классов и общей сложности системы
Во Flutter этот шаблон можно использовать, например, при создании пользовательского интерфейса, в котором пользователь может переключаться между различными параметрами сортировки или фильтрации, или при обработке различных пользовательских взаимодействий с различными виджетами на экране или форме.
Template method
Паттерн Template method («Шаблонный метод») — это поведенческий шаблон проектирования, который определяет каркас алгоритма в базовом классе, позволяя подклассам предоставлять конкретные реализации для определенных шагов алгоритма.
Реализация:
- Создать абстрактный класс с общим алгоритмом, состоящим из набора шагов.
- Определить абстрактные методы для каждого шага алгоритма, которые будут переопределяться в конкретных классах.
- Реализовать конкретные классы, которые будут наследовать абстрактный класс и переопределять абстрактные методы для реализации своей версии алгоритма.
- В абстрактном классе вызывать методы для каждого шага алгоритма в нужном порядке, но оставляя возможность для переопределения каждого шага в конкретных классах.
- Использовать конкретные подклассы для решения задачи, используя общий алгоритм с возможностью реализации специфических деталей в конкретных классах.
Пример кода
import ’package:meta/meta.dart’;
abstract class FormValidator {
@nonVirtual
void validate() {
checkEmail();
checkPassword();
_checkAgreement();
}
void checkEmail();
void checkPassword();
void _checkAgreement() {
print("Agreement is checked");
}
}
class SignInFormValidator extends FormValidator {
@override
void checkEmail() {
print("Email is valid");
}
@override
void checkPassword() {
print("Password is valid");
}
}
class SignUpFormValidator extends FormValidator {
@override
void checkEmail() {
print("Email is valid and confirmed");
}
@override
void checkPassword() {
print("Password is valid and the second password is equal to the first one");
}
}
void main() {
SignInFormValidator().validate();
print(’ ’);
SignUpFormValidator().validate();
}
Базовый класс определяет структуру алгоритма и вызывает необходимые методы, реализованные его подклассами, для заполнения деталей.
В предоставленном коде перед использованием шаблона определены два класса SignInFormValidator и SignUpFormValidator. Оба класса имеют аналогичный метод validate(), который вызывает одни и те же три метода (_checkEmail(), _checkPassword(), _checkAgreement()) в том же порядке. Однако реализация этих методов различна для каждого класса, что может затруднить поддержку и понимание кода.
В коде после использования шаблона определяется абстрактный класс FormValidator. Этот класс содержит метод validate(), который определяет каркас алгоритма путем вызова методов checkEmail() и CheckPassword(), а также имеет метод _checkAgreement(), который определен в базовом классе.
Два конкретных класса SignInFormValidator и SignUpFormValidator определены как подклассы FormValidator, которые предоставляют конкретные реализации для методов checkEmail() и CheckPassword(), которые остаются абстрактными в базовом классе.
Таким образом, используя шаблон шаблонного метода, он позволяет извлечь общий метод (структуру алгоритма) и сохранить реализацию конкретных методов в подклассах.
Шаблонный метод позволяет создать общую схему алгоритма. При этом некоторые шаги реализуются в базовом классе, а другие оставляются для реализации подклассами. Это позволяет повторно использовать код и разделять проблемы, поскольку общие шаги могут быть размещены в базовом классе, а подклассы могут сосредоточиться на предоставлении конкретной реализации для определенных шагов.
Плюсы:
- DRY подход, общие шаги реализуются один раз в базовом классе, а не дублируются в нескольких подклассах.
- Может повысить читаемость и понятность кода, поскольку общие шаги сгруппированы в одном месте.
Минусы:
- Может сделать код менее гибким, поскольку подклассам может потребоваться следовать конкретной реализации, описанной базовым классом.
- Шаблонный метод может быть сложным для понимания и обслуживания, если структура и алгоритм не разработаны должным образом.
Bridge
Паттерн Bridge («Мост») — это структурный шаблон проектирования, используемый для отделения абстракции от ее реализации, чтобы они могли изменяться независимо. Шаблон включает в себя интерфейс (абстракцию), который определяет методы для выполняемых операций, и класс реализации, который обеспечивает фактическую реализацию методов, определенных в интерфейсе.
Реализация:
- Создать интерфейс или абстрактный класс для абстракции, который будет содержать методы для работы с реализацией.
- Создать интерфейс или абстрактный класс для реализации, который будет содержать методы, которые должна реализовать конкретная реализация.
- Реализовать конкретные классы для абстракции и реализации, которые будут реализовывать соответствующие интерфейсы или абстрактные классы.
- Установить связь между классом абстракции и реализации через конструктор или сеттер.
- Использовать класс абстракции для работы с реализацией через интерфейс или абстрактный класс, изолируя клиентский код от конкретных реализаций.
Пример кода
abstract class SalaryCalculator {
const SalaryCalculator(this._source);
final SalarySource _source;
void calculate();
}
class TotalSalaryCalculator extends SalaryCalculator {
const TotalSalaryCalculator(super.source);
@override
void calculate() {
print(’Calculating total salary...’);
_source.getSalary();
}
}
class MonthSalaryCalculator extends SalaryCalculator {
const MonthSalaryCalculator(super.source);
@override
void calculate() {
print(’Calculating month salary...’);
_source.getSalary();
}
}
abstract class SalarySource {
void getSalary();
}
class WhiteSalarySource extends SalarySource {
@override
void getSalary() {
print(’Working with white salary...’);
}
}
class BlackSalarySource extends SalarySource {
@override
void getSalary() {
print(’Working with black salary...’);
}
}
void main() {
final SalaryCalculator calc = MonthSalaryCalculator(WhiteSalarySource());
calc.calculate();
}
В нем есть абстракции SalaryCalculator и SalarySource и классы их реализации TotalSalaryCalculator, MonthSalaryCalculator и WhiteSalarySource, BlackSalarySource.
SalaryCalculator имеет экземпляр SalarySource, а SalarySource имеет определенные методы, которые реализуются WhiteSalarySource, BlackSalarySource.
Класс SalaryCalculator имеет метод calculate(), который использует _source.getSalary(), который реализуется фактическим источником заработной платы.
Плюсы:
- Отделение абстракции от ее реализации может сделать код более гибким и простым в понимании и обслуживании.
- Изменения в реализацию могут быть внесены без ущерба для кода, использующего абстракцию.
- Код можно легче использовать повторно, поскольку реализацию можно заменить, не затрагивая код, использующий абстракцию.
Недостатком шаблона Bridge является то, что он может усложнить код из-за дополнительных уровней абстракции.
Builder
Паттерн Builder («Строитель») — это порождающий шаблон проектирования, который позволяет создавать сложные объекты с помощью вспомогательного объекта, а не напрямую вызывать конструктор. Он отделяет построение объекта от его представления, так что один и тот же процесс построения может создавать разные представления. Этот шаблон часто используется для создания объектов, имеющих множество необязательных полей или настроек.
Реализация:
- Создать интерфейс или абстрактный класс для Строителя, который будет содержать методы для создания различных частей объекта.
- Реализовать конкретные классы-строители, которые будут реализовывать интерфейс или абстрактный класс Строителя и определять конкретные реализации методов для создания частей объекта.
- Создать интерфейс или абстрактный класс для Продукта, который будет содержать методы для получения различных частей объекта.
- Реализовать конкретные классы для Продукта, которые будут реализовывать интерфейс или абстрактный класс Продукта и содержать информацию о созданных частях объекта.
- Создать класс Директор, который будет использовать Строителя для создания объекта и конфигурирования его частей.
- Использовать класс Директор для создания экземпляра Продукта с нужными параметрами.
Пример кода
abstract class Builder {
void buildPartA();
void buildPartB();
void buildPartC();
}
class ProductBuilder extends Builder {
final Product product = Product();
@override
void buildPartA() {
product.addPart(’partA’);
}
@override
void buildPartB() {
product.addPart(’partB’);
}
@override
void buildPartC() {
product.addPart(’partC’);
}
Product get result => product;
}
class Product {
final List _parts = [];
void addPart(String part) {
_parts.add(part);
}
void showParts() {
print(_parts);
}
}
class Director {
Builder? _builder;
void setBuilder(Builder builder) {
_builder = builder;
}
void buildMinimalViableProduct() {
_builder?.buildPartA();
}
void buildFullFeaturedProduct() {
_builder?.buildPartA();
_builder?.buildPartB();
_builder?.buildPartC();
}
}
void main() {
final Director director = Director();
final ProductBuilder builder = ProductBuilder();
director.setBuilder(builder);
print(’Building MVP’);
director.buildMinimalViableProduct();
builder.result.showParts();
print(’Building FFP’);
director.buildFullFeaturedProduct();
builder.result.showParts();
}
Плюсы:
- Он позволяет просто строить сложные объекты шаг за шагом.
- Он отделяет построение объекта от его представления, делая процесс более ясным и легким для понимания.
- Он позволяет создавать различные представления объектов одного и того же типа, используя один и тот же код построения.
- Он позволяет легко добавлять новые функции к объекту без изменения кода построения.
Минусы:
- Может сделать код труднее для чтения, поскольку для строителя и создаваемого объекта необходимо создавать отдельные классы или методы.
- Избыточен для простых объектов
Flyweight
Паттерн Flyweight («Приспособленец») — это структурный шаблон проектирования, который позволяет совместно использовать относительно небольшое количество экземпляров класса среди многих других объектов с целью экономии памяти и повышения производительности. Идея состоит в том, чтобы использовать фабричный метод для создания экземпляров класса и сохранения этих экземпляров на карте, чтобы их можно было повторно использовать позже.
Реализация:
- Идентифицировать общие свойства и методы объектов, которые можно сделать общими для всех экземпляров.
- Создать класс Flyweight, который будет содержать эти общие свойства и методы.
- Создать класс FlyweightFactory, который будет отвечать за создание и хранение экземпляров Flyweight.
- При запросе нового объекта, использовать FlyweightFactory для получения существующего экземпляра Flyweight или создания нового, если такого еще не существует.
- Использовать полученный экземпляр Flyweight для установки специфических свойств и вызова специфических методов для каждого объекта.
Пример кода
class FlyweightFactory {
static final Map flyweights = {};
static Flyweight? getFlyweight(String key) {
if (flyweights.containsKey(key)) {
return flyweights[key];
} else {
final flyweight = Flyweight(key);
flyweights[key] = flyweight;
return flyweight;
}
}
}
class Flyweight {
const Flyweight(this.intrinsicState);
final String intrinsicState;
void operation(String extrinsicState) {
print(’Внутреннее состояние: $intrinsicState, Внешнее состояние: $extrinsicState’);
}
}
void main() {
final Flyweight? flyweight1 = FlyweightFactory.getFlyweight(’flyweight1′);
final Flyweight? flyweight2 = FlyweightFactory.getFlyweight(’flyweight1′);
final Flyweight? flyweight3 = FlyweightFactory.getFlyweight(’flyweight2′);
// В данном случае, flyweight1 и flyweight2 ссылаются на один и тот же объект
// в кэше flyweights, что делает их легковесными
flyweight1?.operation(’state1′);
flyweight2?.operation(’state2′);
flyweight3?.operation(’state3′);