Какие знаешь порождающие паттерны?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Порождающие (Creational) паттерны в Dart/Flutter
Порождающие паттерны занимаются созданием объектов. Они отделяют процесс создания от самого объекта, делая систему независимой от способа создания и состава объектов.
1. Singleton (Одиночка)
Обеспечивает, что у класса только один экземпляр, и предоставляет глобальную точку доступа к нему.
// ❌ Наивная реализация
class AppConfig {
static AppConfig? _instance;
AppConfig._private();
factory AppConfig() {
_instance ??= AppConfig._private();
return _instance!;
}
String apiUrl = 'https://api.example.com';
}
// ✅ Лучше — через const
class AppConfig {
static const AppConfig _instance = AppConfig._();
const AppConfig._();
factory AppConfig() {
return _instance;
}
final String apiUrl = 'https://api.example.com';
}
// Использование
final config = AppConfig();
final config2 = AppConfig();
print(identical(config, config2)); // true
Применение: логирование, конфигурация, подключение к БД, кеш.
2. Factory (Фабрика)
Предоставляет интерфейс для создания объектов, но оставляет подклассам решение о том, какой класс инстанцировать.
// Абстрактный класс
abstract class Animal {
String sound();
// Factory конструктор
factory Animal(String type) {
switch (type) {
case 'dog':
return Dog();
case 'cat':
return Cat();
case 'bird':
return Bird();
default:
throw ArgumentError('Unknown animal type: $type');
}
}
}
class Dog implements Animal {
@override
String sound() => 'Woof!';
}
class Cat implements Animal {
@override
String sound() => 'Meow!';
}
class Bird implements Animal {
@override
String sound() => 'Tweet!';
}
// Использование
final dog = Animal('dog');
final cat = Animal('cat');
print(dog.sound()); // Woof!
print(cat.sound()); // Meow!
Применение: создание разных видов объектов с одного интерфейса.
3. Abstract Factory (Абстрактная фабрика)
Предоставляет интерфейс для создания семейств связанных или зависимых объектов без указания их конкретных классов.
// Абстрактная фабрика
abstract class UIFactory {
Button createButton();
TextField createTextField();
}
// Компоненты для iOS
class IOSButton implements Button {
@override
void render() => print('Rendering iOS button');
}
class IOSTextField implements TextField {
@override
void render() => print('Rendering iOS text field');
}
class IOSFactory implements UIFactory {
@override
Button createButton() => IOSButton();
@override
TextField createTextField() => IOSTextField();
}
// Компоненты для Android
class AndroidButton implements Button {
@override
void render() => print('Rendering Android button');
}
class AndroidTextField implements TextField {
@override
void render() => print('Rendering Android text field');
}
class AndroidFactory implements UIFactory {
@override
Button createButton() => AndroidButton();
@override
TextField createTextField() => AndroidTextField();
}
// Использование
void createUI(UIFactory factory) {
final button = factory.createButton();
final textField = factory.createTextField();
button.render();
textField.render();
}
final isAndroid = true;
final factory = isAndroid ? AndroidFactory() : IOSFactory();
createUI(factory);
Применение: создание тем (светлая/тёмная), платформо-зависимые компоненты.
4. Builder (Строитель)
Отделяет конструирование сложного объекта от его представления, позволяя пошагово конструировать объект.
class User {
final String id;
final String name;
final String? email;
final String? phone;
final int? age;
final String? address;
// Приватный конструктор
const User._(
this.id,
this.name,
this.email,
this.phone,
this.age,
this.address,
);
}
// Builder
class UserBuilder {
late String _id;
late String _name;
String? _email;
String? _phone;
int? _age;
String? _address;
UserBuilder(String id, String name) {
_id = id;
_name = name;
}
UserBuilder setEmail(String email) {
_email = email;
return this;
}
UserBuilder setPhone(String phone) {
_phone = phone;
return this;
}
UserBuilder setAge(int age) {
_age = age;
return this;
}
UserBuilder setAddress(String address) {
_address = address;
return this;
}
User build() {
return User._(
_id,
_name,
_email,
_phone,
_age,
_address,
);
}
}
// Использование
final user = UserBuilder('1', 'John')
.setEmail('john@example.com')
.setAge(30)
.setAddress('123 Main St')
.build();
print(user.name); // John
print(user.email); // john@example.com
Лучше — использовать freezed:
@freezed
class User with _$User {
const factory User({
required String id,
required String name,
String? email,
String? phone,
int? age,
String? address,
}) = _User;
}
// Использование
final user = User(
id: '1',
name: 'John',
email: 'john@example.com',
age: 30,
);
// copyWith для создания изменённой копии
final updatedUser = user.copyWith(age: 31);
Применение: конструирование сложных объектов, конфигурация.
5. Prototype (Прототип)
Предоставляет возможность копировать объект без знания его точного класса.
abstract class Prototype {
Prototype clone();
}
class User implements Prototype {
String id;
String name;
String email;
User(this.id, this.name, this.email);
@override
User clone() {
return User(id, name, email);
}
}
// Использование
final user1 = User('1', 'John', 'john@example.com');
final user2 = user1.clone(); // Копия user1
user2.name = 'Jane'; // Изменение копии не влияет на оригинал
print(user1.name); // John
print(user2.name); // Jane
print(identical(user1, user2)); // false
Лучше — использовать copyWith:
@freezed
class User with _$User {
const factory User({
required String id,
required String name,
required String email,
}) = _User;
}
final user1 = User(id: '1', name: 'John', email: 'john@example.com');
final user2 = user1.copyWith(name: 'Jane'); // Копия с изменениями
Применение: глубокое копирование объектов, undo/redo операции.
Сравнительная таблица
| Паттерн | Цель | Когда использовать |
|---|---|---|
| Singleton | Один экземпляр | Конфиг, логирование, кеш |
| Factory | Создание разных объектов одного интерфейса | Создание животных, транспорта |
| Abstract Factory | Создание семейств объектов | Темы UI, платформо-зависимые компоненты |
| Builder | Конструирование сложных объектов | Конфигурация с множеством параметров |
| Prototype | Копирование объектов | Undo/redo, временные копии |
Практический пример: HTTP клиент
// Singleton для HTTP клиента
class ApiClient {
static final ApiClient _instance = ApiClient._();
late final http.Client _httpClient;
ApiClient._() {
_httpClient = http.Client();
}
factory ApiClient() {
return _instance;
}
// Factory метод для разных типов запросов
ApiRequest createRequest(String endpoint) {
return ApiRequest._(_httpClient, endpoint);
}
}
class ApiRequest {
final http.Client httpClient;
final String endpoint;
Map<String, String> _headers = {};
ApiRequest._(this.httpClient, this.endpoint);
// Builder паттерн
ApiRequest addHeader(String key, String value) {
_headers[key] = value;
return this;
}
Future<Response> get() async {
return await httpClient.get(
Uri.parse(endpoint),
headers: _headers,
);
}
}
// Использование
final apiClient = ApiClient();
final response = await apiClient
.createRequest('https://api.example.com/users')
.addHeader('Authorization', 'Bearer token')
.addHeader('Content-Type', 'application/json')
.get();
Итоги
Порождающие паттерны помогают:
- ✅ Упростить создание объектов
- ✅ Сделать код более гибким
- ✅ Избежать дублирования логики создания
- ✅ Облегчить тестирование через dependency injection
В современном Dart коде часто используют factory конструкторы и freezed вместо классических паттернов Builder и Prototype.