Как применяются принципы SOLID во Flutter?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
SOLID принципы во Flutter
SOLID — это набор пять принципов проектирования кода, которые делают его более масштабируемым и поддерживаемым. Во Flutter они применяются так же, как в любом ООП языке.
S — Single Responsibility (Одна ответственность)
Каждый класс должен иметь только одну причину для изменения.
Плохо — слишком много ответственности
class UserScreen extends StatefulWidget {
@override
State<UserScreen> createState() => _UserScreenState();
}
class _UserScreenState extends State<UserScreen> {
// Это делает ВСЁ: UI, API запросы, логика бизнеса, парсинг
Future<void> fetchUser(String id) async {
final response = await http.get(Uri.parse(https://api.example.com/users/$id));
if (response.statusCode == 200) {
final json = jsonDecode(response.body);
user = User.fromJson(json);
// Бизнес логика здесь
if (user.age < 18) {
user.isAdult = false;
}
setState(() {});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(User)),
body: // построение UI
);
}
}
Хорошо — разделены ответственности
// Слой Domain — бизнес логика
class User {
final String id;
final String name;
final int age;
bool get isAdult => age >= 18;
}
// Слой Data — работа с API
class UserRepository {
final http.Client httpClient;
UserRepository(this.httpClient);
Future<User> getUserById(String id) async {
final response = await httpClient.get(Uri.parse(https://api.example.com/users/$id));
if (response.statusCode == 200) {
return User.fromJson(jsonDecode(response.body));
} else {
throw Exception(Failed to load user);
}
}
}
// Слой Application — use case
class FetchUserUseCase {
final UserRepository repository;
FetchUserUseCase(this.repository);
Future<User> call(String id) => repository.getUserById(id);
}
// Слой Presentation — только UI
class UserScreen extends StatefulWidget {
final FetchUserUseCase fetchUserUseCase;
const UserScreen({required this.fetchUserUseCase});
@override
State<UserScreen> createState() => _UserScreenState();
}
class _UserScreenState extends State<UserScreen> {
late User user;
@override
void initState() {
super.initState();
_loadUser();
}
Future<void> _loadUser() async {
user = await widget.fetchUserUseCase(user_id);
setState(() {});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(user.name)),
body: Text(Age: ${user.age}, Adult: ${user.isAdult}),
);
}
}
O — Open/Closed (Открыт для расширения, закрыт для модификации)
Классы должны быть открыты для расширения (через наследование), но закрыты для модификации.
Плохо — нужно менять исходный код
// Когда добавляется новый тип платежа — нужно менять класс
class PaymentProcessor {
void processPayment(String type, double amount) {
if (type == credit_card) {
// Логика для кредитной карты
} else if (type == paypal) {
// Логика для PayPal
} else if (type == apple_pay) {
// Логика для Apple Pay
}
}
}
Хорошо — расширяемо через полиморфизм
// Абстракция
abstract class PaymentMethod {
Future<bool> processPayment(double amount);
}
// Конкретные реализации
class CreditCardPayment implements PaymentMethod {
@override
Future<bool> processPayment(double amount) async {
// Логика для кредитной карты
return true;
}
}
class PayPalPayment implements PaymentMethod {
@override
Future<bool> processPayment(double amount) async {
// Логика для PayPal
return true;
}
}
class ApplePayPayment implements PaymentMethod {
@override
Future<bool> processPayment(double amount) async {
// Логика для Apple Pay
return true;
}
}
// Обработчик — ЗАКРЫТ для изменений
class PaymentProcessor {
final PaymentMethod _paymentMethod;
PaymentProcessor(this._paymentMethod);
Future<bool> process(double amount) => _paymentMethod.processPayment(amount);
}
// Использование
final creditCard = CreditCardPayment();
final processor = PaymentProcessor(creditCard);
await processor.process(100); // Добавить новый тип платежа — просто создать новый класс
L — Liskov Substitution (Подстановка Лисков)
Объекты подклассов должны корректно заменять объекты суперклассов.
Плохо — подкласс нарушает контракт
abstract class Bird {
void fly(); // Контракт: все птицы летают
}
class Sparrow implements Bird {
@override
void fly() => print(Воробей летит);
}
class Penguin implements Bird {
@override
void fly() => throw Exception(Пингвины не летают); // Нарушение контракта!
}
Хорошо — правильная иерархия
abstract class Animal {
void move();
}
class Bird extends Animal {
@override
void move() => print(Лечу);
}
class Sparrow extends Bird {}
class Penguin extends Animal {
@override
void move() => print(Плыву);
}
// Теперь можно подставлять любой Animal и не беспокоиться
void moveAnimal(Animal animal) {
animal.move();
}
moveAnimal(Sparrow()); // OK
moveAnimal(Penguin()); // OK
I — Interface Segregation (Разделение интерфейсов)
Клиенты не должны зависеть от интерфейсов, которые они не используют.
Плохо — жирный интерфейс
abstract class Worker {
void work();
void eat();
void sleep();
void code(); // Не все рабочие кодят!
void design(); // Не все рабочие дизайнят!
}
class Developer implements Worker {
@override
void work() => print(Кодю);
@override
void code() => print(Пишу код);
@override
void eat() => print(Ем);
@override
void sleep() => print(Сплю);
@override
void design() => throw UnimplementedError(); // Ненужный метод
}
Хорошо — тонкие интерфейсы
abstract class Worker {
void work();
void eat();
void sleep();
}
abstract class Coder {
void code();
}
abstract class Designer {
void design();
}
class Developer implements Worker, Coder {
@override
void work() => print(Кодю);
@override
void code() => print(Пишу код);
@override
void eat() => print(Ем);
@override
void sleep() => print(Сплю);
}
class UiDesigner implements Worker, Designer {
@override
void work() => print(Дизайню);
@override
void design() => print(Рисую интерфейсы);
@override
void eat() => print(Ем);
@override
void sleep() => print(Сплю);
}
D — Dependency Inversion (Инверсия зависимостей)
Зависимость должна быть от абстракций, а не от конкретных классов.
Плохо — прямая зависимость
class UserService {
final UserDatabase database = UserDatabase(); // Жёсткая зависимость
Future<User?> getUser(String id) {
return database.getUser(id);
}
}
class UserDatabase {
Future<User?> getUser(String id) async {
// SQL запрос
}
}
Хорошо — инверсия через инъекцию
// Абстракция
abstract class UserDataSource {
Future<User?> getUser(String id);
}
// Конкретная реализация
class SqliteUserDatabase implements UserDataSource {
@override
Future<User?> getUser(String id) async {
// SQLite запрос
}
}
class FirebaseUserDatabase implements UserDataSource {
@override
Future<User?> getUser(String id) async {
// Firebase запрос
}
}
// Сервис зависит от АБСТРАКЦИИ
class UserService {
final UserDataSource dataSource;
UserService(this.dataSource); // Инъекция
Future<User?> getUser(String id) => dataSource.getUser(id);
}
// Использование — легко переключать реализации
final sqliteDb = SqliteUserDatabase();
final userService = UserService(sqliteDb);
// Или
final firebaseDb = FirebaseUserDatabase();
final userService2 = UserService(firebaseDb); // Тестирование легче
Практический пример: Полное приложение с SOLID
// Domain — бизнес логика
class Post {
final int id;
final String title;
final String content;
Post({required this.id, required this.title, required this.content});
}
// Data — абстракция источника
abstract class PostDataSource {
Future<List<Post>> getPosts();
}
// Конкретная реализация
class ApiPostDataSource implements PostDataSource {
@override
Future<List<Post>> getPosts() async {
final response = await http.get(Uri.parse(https://api.example.com/posts));
if (response.statusCode == 200) {
final List json = jsonDecode(response.body);
return json.map((p) => Post(
id: p[id],
title: p[title],
content: p[content],
)).toList();
}
throw Exception(Failed to load posts);
}
}
// Application
class GetPostsUseCase {
final PostDataSource dataSource;
GetPostsUseCase(this.dataSource);
Future<List<Post>> call() => dataSource.getPosts();
}
// Presentation
class PostsScreen extends StatefulWidget {
final GetPostsUseCase getPostsUseCase;
const PostsScreen({required this.getPostsUseCase});
@override
State<PostsScreen> createState() => _PostsScreenState();
}
class _PostsScreenState extends State<PostsScreen> {
late Future<List<Post>> _postsFuture;
@override
void initState() {
super.initState();
_postsFuture = widget.getPostsUseCase();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(Posts)),
body: FutureBuilder<List<Post>>(
future: _postsFuture,
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
final post = snapshot.data![index];
return ListTile(
title: Text(post.title),
subtitle: Text(post.content),
);
},
);
}
return Center(child: CircularProgressIndicator());
},
),
);
}
}
Проверка SOLID в коде
Прежде чем сдавать код:
- S — каждый класс решает одну задачу?
- O — можно добавить новую функцию без изменения старого кода?
- L — подклассы безопасно заменяют супер-классы?
- I — классы не зависят от ненужных методов?
- D — зависимости инъектируются, а не жёстко закодированы?
Вывод: SOLID — это основа масштабируемого кода. Во Flutter особенно важны S (разделение), O (полиморфизм), D (инъекция). Используй абстракции (abstract classes/interfaces) и слойную архитектуру.